1 /*
2  * Copyright (C) 2015 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.widgets;
17 
18 import static androidx.constraintlayout.core.LinearSystem.DEBUG;
19 import static androidx.constraintlayout.core.LinearSystem.FULL_DEBUG;
20 import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT;
21 import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.WRAP_CONTENT;
22 
23 import androidx.constraintlayout.core.Cache;
24 import androidx.constraintlayout.core.LinearSystem;
25 import androidx.constraintlayout.core.SolverVariable;
26 import androidx.constraintlayout.core.state.WidgetFrame;
27 import androidx.constraintlayout.core.widgets.analyzer.ChainRun;
28 import androidx.constraintlayout.core.widgets.analyzer.HorizontalWidgetRun;
29 import androidx.constraintlayout.core.widgets.analyzer.VerticalWidgetRun;
30 import androidx.constraintlayout.core.widgets.analyzer.WidgetRun;
31 
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.HashMap;
35 import java.util.HashSet;
36 
37 /**
38  * Implements a constraint Widget model supporting constraints relations between other widgets.
39  * <p>
40  * The widget has various anchors (i.e. Left, Top, Right, Bottom, representing their respective
41  * sides, as well as Baseline, Center_X and Center_Y). Connecting anchors from one widget to another
42  * represents a constraint relation between the two anchors; the {@link LinearSystem} will then
43  * be able to use this model to try to minimize the distances between connected anchors.
44  * </p>
45  * <p>
46  * If opposite anchors are connected (e.g. Left and Right anchors), if they have the same strength,
47  * the widget will be equally pulled toward their respective target anchor positions; if the widget
48  * has a fixed size, this means that the widget will be centered between the two target anchors. If
49  * the widget's size is allowed to adjust, the size of the widget will change to be as large as
50  * necessary so that the widget's anchors and the target anchors' distances are zero.
51  * </p>
52  * Constraints are set by connecting a widget's anchor to another via the
53  * {@link #connect} function.
54  */
55 public class ConstraintWidget {
56     private static final boolean AUTOTAG_CENTER = false;
57     private static final boolean DO_NOT_USE = false;
58     protected static final int SOLVER = 1;
59     protected static final int DIRECT = 2;
60 
61     // apply an intrinsic size when wrap content for spread dimensions
62     private static final boolean USE_WRAP_DIMENSION_FOR_SPREAD = false;
63 
64     ////////////////////////////////////////////////////////////////////////////////////////////////
65     // Graph measurements
66     ////////////////////////////////////////////////////////////////////////////////////////////////
67 
68     public boolean measured = false;
69     public WidgetRun[] run = new WidgetRun[2];
70     public ChainRun horizontalChainRun;
71     public ChainRun verticalChainRun;
72 
73     public HorizontalWidgetRun mHorizontalRun = null;
74     public VerticalWidgetRun mVerticalRun = null;
75 
76     public boolean[] isTerminalWidget = {true, true};
77     boolean mResolvedHasRatio = false;
78     private boolean mMeasureRequested = true;
79     private boolean mOptimizeWrapO = false;
80     private boolean mOptimizeWrapOnResolved = true;
81 
82     private int mWidthOverride = -1;
83     private int mHeightOverride = -1;
84 
85     public WidgetFrame frame = new WidgetFrame(this);
86 
87     public String stringId;
88 
89     // @TODO: add description
getRun(int orientation)90     public WidgetRun getRun(int orientation) {
91         if (orientation == HORIZONTAL) {
92             return mHorizontalRun;
93         } else if (orientation == VERTICAL) {
94             return mVerticalRun;
95         }
96         return null;
97     }
98 
99     private boolean mResolvedHorizontal = false;
100     private boolean mResolvedVertical = false;
101 
102     private boolean mHorizontalSolvingPass = false;
103     private boolean mVerticalSolvingPass = false;
104 
105     // @TODO: add description
setFinalFrame(int left, int top, int right, int bottom, int baseline, int orientation)106     public void setFinalFrame(int left,
107             int top,
108             int right,
109             int bottom,
110             int baseline,
111             int orientation) {
112         setFrame(left, top, right, bottom);
113         setBaselineDistance(baseline);
114         if (orientation == HORIZONTAL) {
115             mResolvedHorizontal = true;
116             mResolvedVertical = false;
117         } else if (orientation == VERTICAL) {
118             mResolvedHorizontal = false;
119             mResolvedVertical = true;
120         } else if (orientation == BOTH) {
121             mResolvedHorizontal = true;
122             mResolvedVertical = true;
123         } else {
124             mResolvedHorizontal = false;
125             mResolvedVertical = false;
126         }
127     }
128 
129     // @TODO: add description
setFinalLeft(int x1)130     public void setFinalLeft(int x1) {
131         mLeft.setFinalValue(x1);
132         mX = x1;
133     }
134 
135     // @TODO: add description
setFinalTop(int y1)136     public void setFinalTop(int y1) {
137         mTop.setFinalValue(y1);
138         mY = y1;
139     }
140 
141     // @TODO: add description
resetSolvingPassFlag()142     public void resetSolvingPassFlag() {
143         mHorizontalSolvingPass = false;
144         mVerticalSolvingPass = false;
145     }
146 
isHorizontalSolvingPassDone()147     public boolean isHorizontalSolvingPassDone() {
148         return mHorizontalSolvingPass;
149     }
150 
isVerticalSolvingPassDone()151     public boolean isVerticalSolvingPassDone() {
152         return mVerticalSolvingPass;
153     }
154 
155     // @TODO: add description
markHorizontalSolvingPassDone()156     public void markHorizontalSolvingPassDone() {
157         mHorizontalSolvingPass = true;
158     }
159 
160     // @TODO: add description
markVerticalSolvingPassDone()161     public void markVerticalSolvingPassDone() {
162         mVerticalSolvingPass = true;
163     }
164 
165     // @TODO: add description
setFinalHorizontal(int x1, int x2)166     public void setFinalHorizontal(int x1, int x2) {
167         if (mResolvedHorizontal) {
168             return;
169         }
170         mLeft.setFinalValue(x1);
171         mRight.setFinalValue(x2);
172         mX = x1;
173         mWidth = x2 - x1;
174         mResolvedHorizontal = true;
175         if (LinearSystem.FULL_DEBUG) {
176             System.out.println("*** SET FINAL HORIZONTAL FOR " + getDebugName()
177                     + " : " + x1 + " -> " + x2 + " (width: " + mWidth + ")");
178         }
179     }
180 
181     // @TODO: add description
setFinalVertical(int y1, int y2)182     public void setFinalVertical(int y1, int y2) {
183         if (mResolvedVertical) {
184             return;
185         }
186         mTop.setFinalValue(y1);
187         mBottom.setFinalValue(y2);
188         mY = y1;
189         mHeight = y2 - y1;
190         if (mHasBaseline) {
191             mBaseline.setFinalValue(y1 + mBaselineDistance);
192         }
193         mResolvedVertical = true;
194         if (LinearSystem.FULL_DEBUG) {
195             System.out.println("*** SET FINAL VERTICAL FOR " + getDebugName()
196                     + " : " + y1 + " -> " + y2 + " (height: " + mHeight + ")");
197         }
198     }
199 
200     // @TODO: add description
setFinalBaseline(int baselineValue)201     public void setFinalBaseline(int baselineValue) {
202         if (!mHasBaseline) {
203             return;
204         }
205         int y1 = baselineValue - mBaselineDistance;
206         int y2 = y1 + mHeight;
207         mY = y1;
208         mTop.setFinalValue(y1);
209         mBottom.setFinalValue(y2);
210         mBaseline.setFinalValue(baselineValue);
211         mResolvedVertical = true;
212     }
213 
isResolvedHorizontally()214     public boolean isResolvedHorizontally() {
215         return mResolvedHorizontal || (mLeft.hasFinalValue() && mRight.hasFinalValue());
216     }
217 
isResolvedVertically()218     public boolean isResolvedVertically() {
219         return mResolvedVertical || (mTop.hasFinalValue() && mBottom.hasFinalValue());
220     }
221 
222     // @TODO: add description
resetFinalResolution()223     public void resetFinalResolution() {
224         mResolvedHorizontal = false;
225         mResolvedVertical = false;
226         mHorizontalSolvingPass = false;
227         mVerticalSolvingPass = false;
228         for (int i = 0, mAnchorsSize = mAnchors.size(); i < mAnchorsSize; i++) {
229             final ConstraintAnchor anchor = mAnchors.get(i);
230             anchor.resetFinalResolution();
231         }
232     }
233 
234     // @TODO: add description
ensureMeasureRequested()235     public void ensureMeasureRequested() {
236         mMeasureRequested = true;
237     }
238 
239     // @TODO: add description
hasDependencies()240     public boolean hasDependencies() {
241         for (int i = 0, mAnchorsSize = mAnchors.size(); i < mAnchorsSize; i++) {
242             final ConstraintAnchor anchor = mAnchors.get(i);
243             if (anchor.hasDependents()) {
244                 return true;
245             }
246         }
247         return false;
248     }
249 
250     // @TODO: add description
hasDanglingDimension(int orientation)251     public boolean hasDanglingDimension(int orientation) {
252         if (orientation == HORIZONTAL) {
253             int horizontalTargets =
254                     (mLeft.mTarget != null ? 1 : 0) + (mRight.mTarget != null ? 1 : 0);
255             return horizontalTargets < 2;
256         } else {
257             int verticalTargets = (mTop.mTarget != null ? 1 : 0)
258                     + (mBottom.mTarget != null ? 1 : 0) + (mBaseline.mTarget != null ? 1 : 0);
259             return verticalTargets < 2;
260         }
261     }
262 
263     // @TODO: add description
hasResolvedTargets(int orientation, int size)264     public boolean hasResolvedTargets(int orientation, int size) {
265         if (orientation == HORIZONTAL) {
266             if (mLeft.mTarget != null && mLeft.mTarget.hasFinalValue()
267                     && mRight.mTarget != null && mRight.mTarget.hasFinalValue()) {
268                 return ((mRight.mTarget.getFinalValue() - mRight.getMargin())
269                         - (mLeft.mTarget.getFinalValue() + mLeft.getMargin())) >= size;
270             }
271         } else {
272             if (mTop.mTarget != null && mTop.mTarget.hasFinalValue()
273                     && mBottom.mTarget != null && mBottom.mTarget.hasFinalValue()) {
274                 return ((mBottom.mTarget.getFinalValue() - mBottom.getMargin())
275                         - (mTop.mTarget.getFinalValue() + mTop.getMargin())) >= size;
276             }
277         }
278         return false;
279     }
280 
281     ////////////////////////////////////////////////////////////////////////////////////////////////
282 
283     public static final int MATCH_CONSTRAINT_SPREAD = 0;
284     public static final int MATCH_CONSTRAINT_WRAP = 1;
285     public static final int MATCH_CONSTRAINT_PERCENT = 2;
286     public static final int MATCH_CONSTRAINT_RATIO = 3;
287     public static final int MATCH_CONSTRAINT_RATIO_RESOLVED = 4;
288 
289     public static final int UNKNOWN = -1;
290     public static final int HORIZONTAL = 0;
291     public static final int VERTICAL = 1;
292     public static final int BOTH = 2;
293 
294     public static final int VISIBLE = 0;
295     public static final int INVISIBLE = 4;
296     public static final int GONE = 8;
297 
298     // Values of the chain styles
299     public static final int CHAIN_SPREAD = 0;
300     public static final int CHAIN_SPREAD_INSIDE = 1;
301     public static final int CHAIN_PACKED = 2;
302 
303     // Values of the wrap behavior in parent
304     public static final int WRAP_BEHAVIOR_INCLUDED = 0; // default
305     public static final int WRAP_BEHAVIOR_HORIZONTAL_ONLY = 1;
306     public static final int WRAP_BEHAVIOR_VERTICAL_ONLY = 2;
307     public static final int WRAP_BEHAVIOR_SKIPPED = 3;
308 
309     // Support for direct resolution
310     public int mHorizontalResolution = UNKNOWN;
311     public int mVerticalResolution = UNKNOWN;
312 
313     private static final int WRAP = -2;
314 
315     private int mWrapBehaviorInParent = WRAP_BEHAVIOR_INCLUDED;
316 
317     public int mMatchConstraintDefaultWidth = MATCH_CONSTRAINT_SPREAD;
318     public int mMatchConstraintDefaultHeight = MATCH_CONSTRAINT_SPREAD;
319     public int[] mResolvedMatchConstraintDefault = new int[2];
320 
321     public int mMatchConstraintMinWidth = 0;
322     public int mMatchConstraintMaxWidth = 0;
323     public float mMatchConstraintPercentWidth = 1;
324     public int mMatchConstraintMinHeight = 0;
325     public int mMatchConstraintMaxHeight = 0;
326     public float mMatchConstraintPercentHeight = 1;
327     public boolean mIsWidthWrapContent;
328     public boolean mIsHeightWrapContent;
329 
330     int mResolvedDimensionRatioSide = UNKNOWN;
331     float mResolvedDimensionRatio = 1.0f;
332 
333     private int[] mMaxDimension = {Integer.MAX_VALUE, Integer.MAX_VALUE};
334     public float mCircleConstraintAngle = Float.NaN;
335     private boolean mHasBaseline = false;
336     private boolean mInPlaceholder;
337 
338     private boolean mInVirtualLayout = false;
339 
isInVirtualLayout()340     public boolean isInVirtualLayout() {
341         return mInVirtualLayout;
342     }
343 
setInVirtualLayout(boolean inVirtualLayout)344     public void setInVirtualLayout(boolean inVirtualLayout) {
345         mInVirtualLayout = inVirtualLayout;
346     }
347 
getMaxHeight()348     public int getMaxHeight() {
349         return mMaxDimension[VERTICAL];
350     }
351 
getMaxWidth()352     public int getMaxWidth() {
353         return mMaxDimension[HORIZONTAL];
354     }
355 
setMaxWidth(int maxWidth)356     public void setMaxWidth(int maxWidth) {
357         mMaxDimension[HORIZONTAL] = maxWidth;
358     }
359 
setMaxHeight(int maxHeight)360     public void setMaxHeight(int maxHeight) {
361         mMaxDimension[VERTICAL] = maxHeight;
362     }
363 
isSpreadWidth()364     public boolean isSpreadWidth() {
365         return mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD
366                 && mDimensionRatio == 0
367                 && mMatchConstraintMinWidth == 0
368                 && mMatchConstraintMaxWidth == 0
369                 && mListDimensionBehaviors[HORIZONTAL] == MATCH_CONSTRAINT;
370     }
371 
isSpreadHeight()372     public boolean isSpreadHeight() {
373         return mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD
374                 && mDimensionRatio == 0
375                 && mMatchConstraintMinHeight == 0
376                 && mMatchConstraintMaxHeight == 0
377                 && mListDimensionBehaviors[VERTICAL] == MATCH_CONSTRAINT;
378     }
379 
setHasBaseline(boolean hasBaseline)380     public void setHasBaseline(boolean hasBaseline) {
381         this.mHasBaseline = hasBaseline;
382     }
383 
getHasBaseline()384     public boolean getHasBaseline() {
385         return mHasBaseline;
386     }
387 
isInPlaceholder()388     public boolean isInPlaceholder() {
389         return mInPlaceholder;
390     }
391 
setInPlaceholder(boolean inPlaceholder)392     public void setInPlaceholder(boolean inPlaceholder) {
393         this.mInPlaceholder = inPlaceholder;
394     }
395 
setInBarrier(int orientation, boolean value)396     protected void setInBarrier(int orientation, boolean value) {
397         mIsInBarrier[orientation] = value;
398     }
399 
400     // @TODO: add description
isInBarrier(int orientation)401     public boolean isInBarrier(int orientation) {
402         return mIsInBarrier[orientation];
403     }
404 
setMeasureRequested(boolean measureRequested)405     public void setMeasureRequested(boolean measureRequested) {
406         mMeasureRequested = measureRequested;
407     }
408 
isMeasureRequested()409     public boolean isMeasureRequested() {
410         return mMeasureRequested && mVisibility != GONE;
411     }
412 
413     // @TODO: add description
setWrapBehaviorInParent(int behavior)414     public void setWrapBehaviorInParent(int behavior) {
415         if (behavior >= 0 && behavior <= WRAP_BEHAVIOR_SKIPPED) {
416             mWrapBehaviorInParent = behavior;
417         }
418     }
419 
getWrapBehaviorInParent()420     public int getWrapBehaviorInParent() {
421         return mWrapBehaviorInParent;
422     }
423 
424     /**
425      * Keep a cache of the last measure cache as we can bypass remeasures during the onMeasure...
426      * the View's measure cache will only be reset in onLayout, so too late for us.
427      */
428     private int mLastHorizontalMeasureSpec = 0;
429     private int mLastVerticalMeasureSpec = 0;
430 
getLastHorizontalMeasureSpec()431     public int getLastHorizontalMeasureSpec() {
432         return mLastHorizontalMeasureSpec;
433     }
434 
getLastVerticalMeasureSpec()435     public int getLastVerticalMeasureSpec() {
436         return mLastVerticalMeasureSpec;
437     }
438 
439     // @TODO: add description
setLastMeasureSpec(int horizontal, int vertical)440     public void setLastMeasureSpec(int horizontal, int vertical) {
441         mLastHorizontalMeasureSpec = horizontal;
442         mLastVerticalMeasureSpec = vertical;
443         setMeasureRequested(false);
444     }
445 
446     /**
447      * Define how the widget will resize
448      */
449     public enum DimensionBehaviour {
450         FIXED, WRAP_CONTENT, MATCH_CONSTRAINT, MATCH_PARENT
451     }
452 
453     // The anchors available on the widget
454     // note: all anchors should be added to the mAnchors array (see addAnchors())
455     public ConstraintAnchor mLeft = new ConstraintAnchor(this, ConstraintAnchor.Type.LEFT);
456     public ConstraintAnchor mTop = new ConstraintAnchor(this, ConstraintAnchor.Type.TOP);
457     public ConstraintAnchor mRight = new ConstraintAnchor(this, ConstraintAnchor.Type.RIGHT);
458     public ConstraintAnchor mBottom = new ConstraintAnchor(this, ConstraintAnchor.Type.BOTTOM);
459     public ConstraintAnchor mBaseline = new ConstraintAnchor(this, ConstraintAnchor.Type.BASELINE);
460     ConstraintAnchor mCenterX = new ConstraintAnchor(this, ConstraintAnchor.Type.CENTER_X);
461     ConstraintAnchor mCenterY = new ConstraintAnchor(this, ConstraintAnchor.Type.CENTER_Y);
462     public ConstraintAnchor mCenter = new ConstraintAnchor(this, ConstraintAnchor.Type.CENTER);
463 
464     public static final int ANCHOR_LEFT = 0;
465     public static final int ANCHOR_RIGHT = 1;
466     public static final int ANCHOR_TOP = 2;
467     public static final int ANCHOR_BOTTOM = 3;
468     public static final int ANCHOR_BASELINE = 4;
469 
470     public ConstraintAnchor[] mListAnchors = {mLeft, mRight, mTop, mBottom, mBaseline, mCenter};
471     protected ArrayList<ConstraintAnchor> mAnchors = new ArrayList<>();
472 
473     private boolean[] mIsInBarrier = new boolean[2];
474 
475     // The horizontal and vertical behaviour for the widgets' dimensions
476     static final int DIMENSION_HORIZONTAL = 0;
477     static final int DIMENSION_VERTICAL = 1;
478     public DimensionBehaviour[] mListDimensionBehaviors =
479             {DimensionBehaviour.FIXED, DimensionBehaviour.FIXED};
480 
481     // Parent of this widget
482     public ConstraintWidget mParent = null;
483 
484     // Dimensions of the widget
485     int mWidth = 0;
486     int mHeight = 0;
487     public float mDimensionRatio = 0;
488     protected int mDimensionRatioSide = UNKNOWN;
489 
490     // Origin of the widget
491     protected int mX = 0;
492     protected int mY = 0;
493     int mRelX = 0;
494     int mRelY = 0;
495 
496     // Root offset
497     protected int mOffsetX = 0;
498     protected int mOffsetY = 0;
499 
500     // Baseline distance relative to the top of the widget
501     int mBaselineDistance = 0;
502 
503     // Minimum sizes for the widget
504     protected int mMinWidth;
505     protected int mMinHeight;
506 
507     // Percentages used for biasing one connection over another when dual connections
508     // of the same strength exist
509     public static float DEFAULT_BIAS = 0.5f;
510     float mHorizontalBiasPercent = DEFAULT_BIAS;
511     float mVerticalBiasPercent = DEFAULT_BIAS;
512 
513     // The companion widget (typically, the real widget we represent)
514     private Object mCompanionWidget;
515 
516     // This is used to possibly "skip" a position while inside a container. For example,
517     // a container like Table can use this to implement empty cells
518     // (the item positioned after the empty cell will have a skip value of 1)
519     private int mContainerItemSkip = 0;
520 
521     // Contains the visibility status of the widget (VISIBLE, INVISIBLE, or GONE)
522     private int mVisibility = VISIBLE;
523     // Contains if this widget is animated. Currently only affects gone behaviour
524     private boolean mAnimated = false;
525     private String mDebugName = null;
526     private String mType = null;
527 
528     int mDistToTop;
529     int mDistToLeft;
530     int mDistToRight;
531     int mDistToBottom;
532     boolean mLeftHasCentered;
533     boolean mRightHasCentered;
534     boolean mTopHasCentered;
535     boolean mBottomHasCentered;
536     boolean mHorizontalWrapVisited;
537     boolean mVerticalWrapVisited;
538     boolean mGroupsToSolver = false;
539 
540     // Chain support
541     int mHorizontalChainStyle = CHAIN_SPREAD;
542     int mVerticalChainStyle = CHAIN_SPREAD;
543     boolean mHorizontalChainFixedPosition;
544     boolean mVerticalChainFixedPosition;
545 
546     public float[] mWeight = {UNKNOWN, UNKNOWN};
547 
548     protected ConstraintWidget[] mListNextMatchConstraintsWidget = {null, null};
549     protected ConstraintWidget[] mNextChainWidget = {null, null};
550 
551     ConstraintWidget mHorizontalNextWidget = null;
552     ConstraintWidget mVerticalNextWidget = null;
553 
554     // TODO: see if we can make this simpler
555 
556     // @TODO: add description
reset()557     public void reset() {
558         mLeft.reset();
559         mTop.reset();
560         mRight.reset();
561         mBottom.reset();
562         mBaseline.reset();
563         mCenterX.reset();
564         mCenterY.reset();
565         mCenter.reset();
566         mParent = null;
567         mCircleConstraintAngle = Float.NaN;
568         mWidth = 0;
569         mHeight = 0;
570         mDimensionRatio = 0;
571         mDimensionRatioSide = UNKNOWN;
572         mX = 0;
573         mY = 0;
574         mOffsetX = 0;
575         mOffsetY = 0;
576         mBaselineDistance = 0;
577         mMinWidth = 0;
578         mMinHeight = 0;
579         mHorizontalBiasPercent = DEFAULT_BIAS;
580         mVerticalBiasPercent = DEFAULT_BIAS;
581         mListDimensionBehaviors[DIMENSION_HORIZONTAL] = DimensionBehaviour.FIXED;
582         mListDimensionBehaviors[DIMENSION_VERTICAL] = DimensionBehaviour.FIXED;
583         mCompanionWidget = null;
584         mContainerItemSkip = 0;
585         mVisibility = VISIBLE;
586         mType = null;
587         mHorizontalWrapVisited = false;
588         mVerticalWrapVisited = false;
589         mHorizontalChainStyle = CHAIN_SPREAD;
590         mVerticalChainStyle = CHAIN_SPREAD;
591         mHorizontalChainFixedPosition = false;
592         mVerticalChainFixedPosition = false;
593         mWeight[DIMENSION_HORIZONTAL] = UNKNOWN;
594         mWeight[DIMENSION_VERTICAL] = UNKNOWN;
595         mHorizontalResolution = UNKNOWN;
596         mVerticalResolution = UNKNOWN;
597         mMaxDimension[HORIZONTAL] = Integer.MAX_VALUE;
598         mMaxDimension[VERTICAL] = Integer.MAX_VALUE;
599         mMatchConstraintDefaultWidth = MATCH_CONSTRAINT_SPREAD;
600         mMatchConstraintDefaultHeight = MATCH_CONSTRAINT_SPREAD;
601         mMatchConstraintPercentWidth = 1;
602         mMatchConstraintPercentHeight = 1;
603         mMatchConstraintMaxWidth = Integer.MAX_VALUE;
604         mMatchConstraintMaxHeight = Integer.MAX_VALUE;
605         mMatchConstraintMinWidth = 0;
606         mMatchConstraintMinHeight = 0;
607         mResolvedHasRatio = false;
608         mResolvedDimensionRatioSide = UNKNOWN;
609         mResolvedDimensionRatio = 1f;
610         mGroupsToSolver = false;
611         isTerminalWidget[HORIZONTAL] = true;
612         isTerminalWidget[VERTICAL] = true;
613         mInVirtualLayout = false;
614         mIsInBarrier[HORIZONTAL] = false;
615         mIsInBarrier[VERTICAL] = false;
616         mMeasureRequested = true;
617         mResolvedMatchConstraintDefault[HORIZONTAL] = 0;
618         mResolvedMatchConstraintDefault[VERTICAL] = 0;
619         mWidthOverride = -1;
620         mHeightOverride = -1;
621     }
622 
623     ///////////////////////////////////SERIALIZE///////////////////////////////////////////////
624 
serializeAnchor(StringBuilder ret, String side, ConstraintAnchor a)625     private void serializeAnchor(StringBuilder ret, String side, ConstraintAnchor a) {
626         if (a.mTarget == null) {
627             return;
628         }
629         ret.append(side);
630         ret.append(" : [ '");
631         ret.append(a.mTarget);
632         ret.append("',");
633         ret.append(a.mMargin);
634         ret.append(",");
635         ret.append(a.mGoneMargin);
636         ret.append(",");
637         ret.append(" ] ,\n");
638     }
639 
serializeCircle(StringBuilder ret, ConstraintAnchor a, float angle)640     private void serializeCircle(StringBuilder ret, ConstraintAnchor a, float angle) {
641         if (a.mTarget == null || Float.isNaN(angle)) {
642             return;
643         }
644 
645         ret.append("circle : [ '");
646         ret.append(a.mTarget);
647         ret.append("',");
648         ret.append(a.mMargin);
649         ret.append(",");
650         ret.append(angle);
651         ret.append(",");
652         ret.append(" ] ,\n");
653     }
654 
serializeAttribute(StringBuilder ret, String type, float value, float def)655     private void serializeAttribute(StringBuilder ret, String type, float value, float def) {
656         if (value == def) {
657             return;
658         }
659         ret.append(type);
660         ret.append(" :   ");
661         ret.append(value);
662         ret.append(",\n");
663     }
664 
serializeAttribute(StringBuilder ret, String type, int value, int def)665     private void serializeAttribute(StringBuilder ret, String type, int value, int def) {
666         if (value == def) {
667             return;
668         }
669         ret.append(type);
670         ret.append(" :   ");
671         ret.append(value);
672         ret.append(",\n");
673     }
674 
serializeAttribute(StringBuilder ret, String type, String value, String def)675     private void serializeAttribute(StringBuilder ret, String type, String value, String def) {
676         if (def.equals(value)) {
677             return;
678         }
679         ret.append(type);
680         ret.append(" :   ");
681         ret.append(value);
682         ret.append(",\n");
683     }
684 
serializeDimensionRatio(StringBuilder ret, String type, float value, int whichSide)685     private void serializeDimensionRatio(StringBuilder ret,
686             String type,
687             float value,
688             int whichSide) {
689         if (value == 0) {
690             return;
691         }
692         ret.append(type);
693         ret.append(" :  [");
694         ret.append(value);
695         ret.append(",");
696         ret.append(whichSide);
697         ret.append("");
698         ret.append("],\n");
699     }
700 
serializeSize(StringBuilder ret, String type, int size, int min, int max, int override, int matchConstraintMin, int matchConstraintDefault, float matchConstraintPercent, float weight)701     private void serializeSize(StringBuilder ret, String type, int size,
702             int min, int max, int override,
703             int matchConstraintMin, int matchConstraintDefault,
704             float matchConstraintPercent,
705             float weight) {
706         ret.append(type);
707         ret.append(" :  {\n");
708         serializeAttribute(ret, "size", size, Integer.MIN_VALUE);
709         serializeAttribute(ret, "min", min, 0);
710         serializeAttribute(ret, "max", max, Integer.MAX_VALUE);
711         serializeAttribute(ret, "matchMin", matchConstraintMin, 0);
712         serializeAttribute(ret, "matchDef", matchConstraintDefault, MATCH_CONSTRAINT_SPREAD);
713         serializeAttribute(ret, "matchPercent", matchConstraintDefault, 1);
714         serializeAttribute(ret, "matchConstraintPercent", matchConstraintPercent, 1);
715         serializeAttribute(ret, "weight", weight, 1);
716         serializeAttribute(ret, "override", override, 1);
717 
718         ret.append("},\n");
719     }
720 
721     /**
722      * Serialize the anchors for JSON5 output
723      * @param ret StringBuilder to be populated
724      * @return the same string builder to alow chaining
725      */
serialize(StringBuilder ret)726     public StringBuilder serialize(StringBuilder ret) {
727         ret.append("{\n");
728         serializeAnchor(ret, "left", mLeft);
729         serializeAnchor(ret, "top", mTop);
730         serializeAnchor(ret, "right", mRight);
731         serializeAnchor(ret, "bottom", mBottom);
732         serializeAnchor(ret, "baseline", mBaseline);
733         serializeAnchor(ret, "centerX", mCenterX);
734         serializeAnchor(ret, "centerY", mCenterY);
735         serializeCircle(ret, mCenter, mCircleConstraintAngle);
736 
737         serializeSize(ret, "width",
738                 mWidth,
739                 mMinWidth,
740                 mMaxDimension[HORIZONTAL],
741                 mWidthOverride,
742                 mMatchConstraintMinWidth,
743                 mMatchConstraintDefaultWidth,
744                 mMatchConstraintPercentWidth,
745                 mWeight[DIMENSION_HORIZONTAL]
746         );
747 
748         serializeSize(ret, "height",
749                 mHeight,
750                 mMinHeight,
751                 mMaxDimension[VERTICAL],
752                 mHeightOverride,
753                 mMatchConstraintMinHeight,
754                 mMatchConstraintDefaultHeight,
755                 mMatchConstraintPercentHeight,
756                 mWeight[DIMENSION_VERTICAL]);
757 
758         serializeDimensionRatio(ret, "dimensionRatio", mDimensionRatio, mDimensionRatioSide);
759         serializeAttribute(ret, "horizontalBias", mHorizontalBiasPercent, DEFAULT_BIAS);
760         serializeAttribute(ret, "verticalBias", mVerticalBiasPercent, DEFAULT_BIAS);
761         ret.append("}\n");
762 
763         return ret;
764     }
765     ///////////////////////////////////END SERIALIZE///////////////////////////////////////////
766 
767     public int horizontalGroup = -1;
768     public int verticalGroup = -1;
769 
770     // @TODO: add description
oppositeDimensionDependsOn(int orientation)771     public boolean oppositeDimensionDependsOn(int orientation) {
772         int oppositeOrientation = (orientation == HORIZONTAL) ? VERTICAL : HORIZONTAL;
773         DimensionBehaviour dimensionBehaviour = mListDimensionBehaviors[orientation];
774         DimensionBehaviour oppositeDimensionBehaviour =
775                 mListDimensionBehaviors[oppositeOrientation];
776         return dimensionBehaviour == MATCH_CONSTRAINT
777                 && oppositeDimensionBehaviour == MATCH_CONSTRAINT;
778         //&& mDimensionRatio != 0;
779     }
780 
781     // @TODO: add description
oppositeDimensionsTied()782     public boolean oppositeDimensionsTied() {
783         return /* isInHorizontalChain() || isInVerticalChain() || */
784                 (mListDimensionBehaviors[HORIZONTAL] == MATCH_CONSTRAINT
785                         && mListDimensionBehaviors[VERTICAL] == MATCH_CONSTRAINT);
786     }
787 
788     // @TODO: add description
hasDimensionOverride()789     public boolean hasDimensionOverride() {
790         return mWidthOverride != -1 || mHeightOverride != -1;
791     }
792 
793     /*-----------------------------------------------------------------------*/
794     // Creation
795     /*-----------------------------------------------------------------------*/
796 
797     /**
798      * Default constructor
799      */
ConstraintWidget()800     public ConstraintWidget() {
801         addAnchors();
802     }
803 
ConstraintWidget(String debugName)804     public ConstraintWidget(String debugName) {
805         addAnchors();
806         setDebugName(debugName);
807     }
808 
809     /**
810      * Constructor
811      *
812      * @param x      x position
813      * @param y      y position
814      * @param width  width of the layout
815      * @param height height of the layout
816      */
ConstraintWidget(int x, int y, int width, int height)817     public ConstraintWidget(int x, int y, int width, int height) {
818         mX = x;
819         mY = y;
820         mWidth = width;
821         mHeight = height;
822         addAnchors();
823     }
824 
ConstraintWidget(String debugName, int x, int y, int width, int height)825     public ConstraintWidget(String debugName, int x, int y, int width, int height) {
826         this(x, y, width, height);
827         setDebugName(debugName);
828     }
829 
830     /**
831      * Constructor
832      *
833      * @param width  width of the layout
834      * @param height height of the layout
835      */
ConstraintWidget(int width, int height)836     public ConstraintWidget(int width, int height) {
837         this(0, 0, width, height);
838     }
839 
840     // @TODO: add description
ensureWidgetRuns()841     public void ensureWidgetRuns() {
842         if (mHorizontalRun == null) {
843             mHorizontalRun = new HorizontalWidgetRun(this);
844         }
845         if (mVerticalRun == null) {
846             mVerticalRun = new VerticalWidgetRun(this);
847         }
848     }
849 
ConstraintWidget(String debugName, int width, int height)850     public ConstraintWidget(String debugName, int width, int height) {
851         this(width, height);
852         setDebugName(debugName);
853     }
854 
855     /**
856      * Reset the solver variables of the anchors
857      */
resetSolverVariables(Cache cache)858     public void resetSolverVariables(Cache cache) {
859         mLeft.resetSolverVariable(cache);
860         mTop.resetSolverVariable(cache);
861         mRight.resetSolverVariable(cache);
862         mBottom.resetSolverVariable(cache);
863         mBaseline.resetSolverVariable(cache);
864         mCenter.resetSolverVariable(cache);
865         mCenterX.resetSolverVariable(cache);
866         mCenterY.resetSolverVariable(cache);
867     }
868 
869     /**
870      * Add all the anchors to the mAnchors array
871      */
addAnchors()872     private void addAnchors() {
873         mAnchors.add(mLeft);
874         mAnchors.add(mTop);
875         mAnchors.add(mRight);
876         mAnchors.add(mBottom);
877         mAnchors.add(mCenterX);
878         mAnchors.add(mCenterY);
879         mAnchors.add(mCenter);
880         mAnchors.add(mBaseline);
881     }
882 
883     /**
884      * Returns true if the widget is the root widget
885      *
886      * @return true if root widget, false otherwise
887      */
isRoot()888     public boolean isRoot() {
889         return mParent == null;
890     }
891 
892     /**
893      * Returns the parent of this widget if there is one
894      *
895      * @return parent
896      */
getParent()897     public ConstraintWidget getParent() {
898         return mParent;
899     }
900 
901     /**
902      * Set the parent of this widget
903      *
904      * @param widget parent
905      */
setParent(ConstraintWidget widget)906     public void setParent(ConstraintWidget widget) {
907         mParent = widget;
908     }
909 
910     /**
911      * Keep track of wrap_content for width
912      */
setWidthWrapContent(boolean widthWrapContent)913     public void setWidthWrapContent(boolean widthWrapContent) {
914         this.mIsWidthWrapContent = widthWrapContent;
915     }
916 
917     /**
918      * Returns true if width is set to wrap_content
919      */
isWidthWrapContent()920     public boolean isWidthWrapContent() {
921         return mIsWidthWrapContent;
922     }
923 
924     /**
925      * Keep track of wrap_content for height
926      */
setHeightWrapContent(boolean heightWrapContent)927     public void setHeightWrapContent(boolean heightWrapContent) {
928         this.mIsHeightWrapContent = heightWrapContent;
929     }
930 
931     /**
932      * Returns true if height is set to wrap_content
933      */
isHeightWrapContent()934     public boolean isHeightWrapContent() {
935         return mIsHeightWrapContent;
936     }
937 
938     /**
939      * Set a circular constraint
940      *
941      * @param target the target widget we will use as the center of the circle
942      * @param angle  the angle (from 0 to 360)
943      * @param radius the radius used
944      */
connectCircularConstraint(ConstraintWidget target, float angle, int radius)945     public void connectCircularConstraint(ConstraintWidget target, float angle, int radius) {
946         immediateConnect(ConstraintAnchor.Type.CENTER, target, ConstraintAnchor.Type.CENTER,
947                 radius, 0);
948         mCircleConstraintAngle = angle;
949     }
950 
951     /**
952      * Returns the type string if set
953      *
954      * @return type (null if not set)
955      */
getType()956     public String getType() {
957         return mType;
958     }
959 
960     /**
961      * Set the type of the widget (as a String)
962      *
963      * @param type type of the widget
964      */
setType(String type)965     public void setType(String type) {
966         mType = type;
967     }
968 
969     /**
970      * Set the visibility for this widget
971      *
972      * @param visibility either VISIBLE, INVISIBLE, or GONE
973      */
setVisibility(int visibility)974     public void setVisibility(int visibility) {
975         mVisibility = visibility;
976     }
977 
978     /**
979      * Returns the current visibility value for this widget
980      *
981      * @return the visibility (VISIBLE, INVISIBLE, or GONE)
982      */
getVisibility()983     public int getVisibility() {
984         return mVisibility;
985     }
986 
987     /**
988      * Set if this widget is animated. Currently only affects gone behaviour
989      *
990      * @param animated if true the widget must be positioned correctly when not visible
991      */
setAnimated(boolean animated)992     public void setAnimated(boolean animated) {
993         mAnimated = animated;
994     }
995 
996     /**
997      * Returns if this widget is animated. Currently only affects gone behaviour
998      *
999      * @return true if ConstraintWidget is used in Animation
1000      */
isAnimated()1001     public boolean isAnimated() {
1002         return mAnimated;
1003     }
1004 
1005     /**
1006      * Returns the name of this widget (used for debug purposes)
1007      *
1008      * @return the debug name
1009      */
getDebugName()1010     public String getDebugName() {
1011         return mDebugName;
1012     }
1013 
1014     /**
1015      * Set the debug name of this widget
1016      */
setDebugName(String name)1017     public void setDebugName(String name) {
1018         mDebugName = name;
1019     }
1020 
1021     /**
1022      * Utility debug function. Sets the names of the anchors in the solver given
1023      * a widget's name. The given name is used as a prefix, resulting in anchors' names
1024      * of the form:
1025      * <p/>
1026      * <ul>
1027      * <li>{name}.left</li>
1028      * <li>{name}.top</li>
1029      * <li>{name}.right</li>
1030      * <li>{name}.bottom</li>
1031      * <li>{name}.baseline</li>
1032      * </ul>
1033      *
1034      * @param system solver used
1035      * @param name   name of the widget
1036      */
setDebugSolverName(LinearSystem system, String name)1037     public void setDebugSolverName(LinearSystem system, String name) {
1038         mDebugName = name;
1039         SolverVariable left = system.createObjectVariable(mLeft);
1040         SolverVariable top = system.createObjectVariable(mTop);
1041         SolverVariable right = system.createObjectVariable(mRight);
1042         SolverVariable bottom = system.createObjectVariable(mBottom);
1043         left.setName(name + ".left");
1044         top.setName(name + ".top");
1045         right.setName(name + ".right");
1046         bottom.setName(name + ".bottom");
1047         SolverVariable baseline = system.createObjectVariable(mBaseline);
1048         baseline.setName(name + ".baseline");
1049     }
1050 
1051     /**
1052      * Create all the system variables for this widget
1053      *
1054      *
1055      */
createObjectVariables(LinearSystem system)1056     public void createObjectVariables(LinearSystem system) {
1057         system.createObjectVariable(mLeft);
1058         system.createObjectVariable(mTop);
1059         system.createObjectVariable(mRight);
1060         system.createObjectVariable(mBottom);
1061         if (mBaselineDistance > 0) {
1062             system.createObjectVariable(mBaseline);
1063         }
1064     }
1065 
1066     /**
1067      * Returns a string representation of the ConstraintWidget
1068      *
1069      * @return string representation of the widget
1070      */
1071     @Override
toString()1072     public String toString() {
1073         return (mType != null ? "type: " + mType + " " : "")
1074                 + (mDebugName != null ? "id: " + mDebugName + " " : "")
1075                 + "(" + mX + ", " + mY + ") - (" + mWidth + " x " + mHeight + ")";
1076     }
1077 
1078     /*-----------------------------------------------------------------------*/
1079     // Position
1080     /*-----------------------------------------------------------------------*/
1081     // The widget position is expressed in two ways:
1082     // - relative to its direct parent container (getX(), getY())
1083     // - relative to the root container (getDrawX(), getDrawY())
1084     // Additionally, getDrawX()/getDrawY() are used when animating the
1085     // widget position on screen
1086     /*-----------------------------------------------------------------------*/
1087 
1088     /**
1089      * Return the x position of the widget, relative to its container
1090      *
1091      * @return x position
1092      */
getX()1093     public int getX() {
1094         if (mParent != null && mParent instanceof ConstraintWidgetContainer) {
1095             return ((ConstraintWidgetContainer) mParent).mPaddingLeft + mX;
1096         }
1097         return mX;
1098     }
1099 
1100     /**
1101      * Return the y position of the widget, relative to its container
1102      *
1103      * @return y position
1104      */
getY()1105     public int getY() {
1106         if (mParent != null && mParent instanceof ConstraintWidgetContainer) {
1107             return ((ConstraintWidgetContainer) mParent).mPaddingTop + mY;
1108         }
1109         return mY;
1110     }
1111 
1112     /**
1113      * Return the width of the widget
1114      *
1115      * @return width width
1116      */
getWidth()1117     public int getWidth() {
1118         if (mVisibility == ConstraintWidget.GONE) {
1119             return 0;
1120         }
1121         return mWidth;
1122     }
1123 
1124     // @TODO: add description
getOptimizerWrapWidth()1125     public int getOptimizerWrapWidth() {
1126         int w = mWidth;
1127         if (mListDimensionBehaviors[DIMENSION_HORIZONTAL] == MATCH_CONSTRAINT) {
1128             if (mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_WRAP) {
1129                 w = Math.max(mMatchConstraintMinWidth, w);
1130             } else if (mMatchConstraintMinWidth > 0) {
1131                 w = mMatchConstraintMinWidth;
1132                 mWidth = w;
1133             } else {
1134                 w = 0;
1135             }
1136             if (mMatchConstraintMaxWidth > 0 && mMatchConstraintMaxWidth < w) {
1137                 w = mMatchConstraintMaxWidth;
1138             }
1139         }
1140         return w;
1141     }
1142 
1143     // @TODO: add description
getOptimizerWrapHeight()1144     public int getOptimizerWrapHeight() {
1145         int h = mHeight;
1146         if (mListDimensionBehaviors[DIMENSION_VERTICAL] == MATCH_CONSTRAINT) {
1147             if (mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_WRAP) {
1148                 h = Math.max(mMatchConstraintMinHeight, h);
1149             } else if (mMatchConstraintMinHeight > 0) {
1150                 h = mMatchConstraintMinHeight;
1151                 mHeight = h;
1152             } else {
1153                 h = 0;
1154             }
1155             if (mMatchConstraintMaxHeight > 0 && mMatchConstraintMaxHeight < h) {
1156                 h = mMatchConstraintMaxHeight;
1157             }
1158         }
1159         return h;
1160     }
1161 
1162     /**
1163      * Return the height of the widget
1164      *
1165      * @return height height
1166      */
getHeight()1167     public int getHeight() {
1168         if (mVisibility == ConstraintWidget.GONE) {
1169             return 0;
1170         }
1171         return mHeight;
1172     }
1173 
1174     /**
1175      * Get a dimension of the widget in a particular orientation.
1176      *
1177      * @return The dimension of the specified orientation.
1178      */
getLength(int orientation)1179     public int getLength(int orientation) {
1180         if (orientation == HORIZONTAL) {
1181             return getWidth();
1182         } else if (orientation == VERTICAL) {
1183             return getHeight();
1184         } else {
1185             return 0;
1186         }
1187     }
1188 
1189     /**
1190      * Return the x position of the widget, relative to the root
1191      * (without animation)
1192      *
1193      * @return x position
1194      */
getRootX()1195     protected int getRootX() {
1196         return mX + mOffsetX;
1197     }
1198 
1199     /**
1200      * Return the y position of the widget, relative to the root
1201      * (without animation)
1202      */
getRootY()1203     protected int getRootY() {
1204         return mY + mOffsetY;
1205     }
1206 
1207     /**
1208      * Return the minimum width of the widget
1209      *
1210      * @return minimum width
1211      */
getMinWidth()1212     public int getMinWidth() {
1213         return mMinWidth;
1214     }
1215 
1216     /**
1217      * Return the minimum height of the widget
1218      *
1219      * @return minimum height
1220      */
getMinHeight()1221     public int getMinHeight() {
1222         return mMinHeight;
1223     }
1224 
1225     /**
1226      * Return the left position of the widget (similar to {@link #getX()})
1227      *
1228      * @return left position of the widget
1229      */
getLeft()1230     public int getLeft() {
1231         return getX();
1232     }
1233 
1234     /**
1235      * Return the top position of the widget (similar to {@link #getY()})
1236      *
1237      * @return top position of the widget
1238      */
getTop()1239     public int getTop() {
1240         return getY();
1241     }
1242 
1243     /**
1244      * Return the right position of the widget
1245      *
1246      * @return right position of the widget
1247      */
getRight()1248     public int getRight() {
1249         return getX() + mWidth;
1250     }
1251 
1252     /**
1253      * Return the bottom position of the widget
1254      *
1255      * @return bottom position of the widget
1256      */
getBottom()1257     public int getBottom() {
1258         return getY() + mHeight;
1259     }
1260 
1261     /**
1262      * Returns all the horizontal margin of the widget.
1263      */
getHorizontalMargin()1264     public int getHorizontalMargin() {
1265         int margin = 0;
1266         if (mLeft != null) {
1267             margin += mLeft.mMargin;
1268         }
1269         if (mRight != null) {
1270             margin += mRight.mMargin;
1271         }
1272         return margin;
1273     }
1274 
1275     /**
1276      * Returns all the vertical margin of the widget
1277      */
getVerticalMargin()1278     public int getVerticalMargin() {
1279         int margin = 0;
1280         if (mLeft != null) {
1281             margin += mTop.mMargin;
1282         }
1283         if (mRight != null) {
1284             margin += mBottom.mMargin;
1285         }
1286         return margin;
1287     }
1288 
1289     /**
1290      * Return the horizontal percentage bias that is used when two opposite connections
1291      * exist of the same strength.
1292      *
1293      * @return horizontal percentage bias
1294      */
getHorizontalBiasPercent()1295     public float getHorizontalBiasPercent() {
1296         return mHorizontalBiasPercent;
1297     }
1298 
1299     /**
1300      * Return the vertical percentage bias that is used when two opposite connections
1301      * exist of the same strength.
1302      *
1303      * @return vertical percentage bias
1304      */
getVerticalBiasPercent()1305     public float getVerticalBiasPercent() {
1306         return mVerticalBiasPercent;
1307     }
1308 
1309     /**
1310      * Return the percentage bias that is used when two opposite connections exist of the same
1311      * strength in a particular orientation.
1312      *
1313      * @param orientation Orientation {@link #HORIZONTAL}/{@link #VERTICAL}.
1314      * @return Respective percentage bias.
1315      */
getBiasPercent(int orientation)1316     public float getBiasPercent(int orientation) {
1317         if (orientation == HORIZONTAL) {
1318             return mHorizontalBiasPercent;
1319         } else if (orientation == VERTICAL) {
1320             return mVerticalBiasPercent;
1321         } else {
1322             return UNKNOWN;
1323         }
1324     }
1325 
1326     /**
1327      * Return true if this widget has a baseline
1328      *
1329      * @return true if the widget has a baseline, false otherwise
1330      */
hasBaseline()1331     public boolean hasBaseline() {
1332         return mHasBaseline;
1333     }
1334 
1335     /**
1336      * Return the baseline distance relative to the top of the widget
1337      *
1338      * @return baseline
1339      */
getBaselineDistance()1340     public int getBaselineDistance() {
1341         return mBaselineDistance;
1342     }
1343 
1344     /**
1345      * Return the companion widget. Typically, this would be the real
1346      * widget we represent with this instance of ConstraintWidget.
1347      *
1348      * @return the companion widget, if set.
1349      */
getCompanionWidget()1350     public Object getCompanionWidget() {
1351         return mCompanionWidget;
1352     }
1353 
1354     /**
1355      * Return the array of anchors of this widget
1356      *
1357      * @return array of anchors
1358      */
getAnchors()1359     public ArrayList<ConstraintAnchor> getAnchors() {
1360         return mAnchors;
1361     }
1362 
1363     /**
1364      * Set the x position of the widget, relative to its container
1365      *
1366      * @param x x position
1367      */
setX(int x)1368     public void setX(int x) {
1369         mX = x;
1370     }
1371 
1372     /**
1373      * Set the y position of the widget, relative to its container
1374      *
1375      * @param y y position
1376      */
setY(int y)1377     public void setY(int y) {
1378         mY = y;
1379     }
1380 
1381     /**
1382      * Set both the origin in (x, y) of the widget, relative to its container
1383      *
1384      * @param x x position
1385      * @param y y position
1386      */
setOrigin(int x, int y)1387     public void setOrigin(int x, int y) {
1388         mX = x;
1389         mY = y;
1390     }
1391 
1392     /**
1393      * Set the offset of this widget relative to the root widget
1394      *
1395      * @param x horizontal offset
1396      * @param y vertical offset
1397      */
setOffset(int x, int y)1398     public void setOffset(int x, int y) {
1399         mOffsetX = x;
1400         mOffsetY = y;
1401     }
1402 
1403     /**
1404      * Set the margin to be used when connected to a widget with a visibility of GONE
1405      *
1406      * @param type       the anchor to set the margin on
1407      * @param goneMargin the margin value to use
1408      */
setGoneMargin(ConstraintAnchor.Type type, int goneMargin)1409     public void setGoneMargin(ConstraintAnchor.Type type, int goneMargin) {
1410         switch (type) {
1411             case LEFT: {
1412                 mLeft.mGoneMargin = goneMargin;
1413             }
1414             break;
1415             case TOP: {
1416                 mTop.mGoneMargin = goneMargin;
1417             }
1418             break;
1419             case RIGHT: {
1420                 mRight.mGoneMargin = goneMargin;
1421             }
1422             break;
1423             case BOTTOM: {
1424                 mBottom.mGoneMargin = goneMargin;
1425             }
1426             break;
1427             case BASELINE: {
1428                 mBaseline.mGoneMargin = goneMargin;
1429             }
1430             break;
1431             case CENTER:
1432             case CENTER_X:
1433             case CENTER_Y:
1434             case NONE:
1435                 break;
1436         }
1437     }
1438 
1439     /**
1440      * Set the width of the widget
1441      *
1442      * @param w width
1443      */
setWidth(int w)1444     public void setWidth(int w) {
1445         mWidth = w;
1446         if (mWidth < mMinWidth) {
1447             mWidth = mMinWidth;
1448         }
1449     }
1450 
1451     /**
1452      * Set the height of the widget
1453      *
1454      * @param h height
1455      */
setHeight(int h)1456     public void setHeight(int h) {
1457         mHeight = h;
1458         if (mHeight < mMinHeight) {
1459             mHeight = mMinHeight;
1460         }
1461     }
1462 
1463     /**
1464      * Set the dimension of a widget in a particular orientation.
1465      *
1466      * @param length      Size of the dimension.
1467      * @param orientation HORIZONTAL or VERTICAL
1468      */
setLength(int length, int orientation)1469     public void setLength(int length, int orientation) {
1470         if (orientation == HORIZONTAL) {
1471             setWidth(length);
1472         } else if (orientation == VERTICAL) {
1473             setHeight(length);
1474         }
1475     }
1476 
1477     /**
1478      * Set the horizontal style when MATCH_CONSTRAINT is set
1479      *
1480      * @param horizontalMatchStyle MATCH_CONSTRAINT_SPREAD or MATCH_CONSTRAINT_WRAP
1481      * @param min                  minimum value
1482      * @param max                  maximum value
1483      * @param percent              Percent width
1484      */
setHorizontalMatchStyle(int horizontalMatchStyle, int min, int max, float percent)1485     public void setHorizontalMatchStyle(int horizontalMatchStyle, int min, int max, float percent) {
1486         mMatchConstraintDefaultWidth = horizontalMatchStyle;
1487         mMatchConstraintMinWidth = min;
1488         mMatchConstraintMaxWidth = (max == Integer.MAX_VALUE) ? 0 : max;
1489         mMatchConstraintPercentWidth = percent;
1490         if (percent > 0 && percent < 1 && mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD) {
1491             mMatchConstraintDefaultWidth = MATCH_CONSTRAINT_PERCENT;
1492         }
1493     }
1494 
1495     /**
1496      * Set the vertical style when MATCH_CONSTRAINT is set
1497      *
1498      * @param verticalMatchStyle MATCH_CONSTRAINT_SPREAD or MATCH_CONSTRAINT_WRAP
1499      * @param min                minimum value
1500      * @param max                maximum value
1501      * @param percent            Percent height
1502      */
setVerticalMatchStyle(int verticalMatchStyle, int min, int max, float percent)1503     public void setVerticalMatchStyle(int verticalMatchStyle, int min, int max, float percent) {
1504         mMatchConstraintDefaultHeight = verticalMatchStyle;
1505         mMatchConstraintMinHeight = min;
1506         mMatchConstraintMaxHeight = (max == Integer.MAX_VALUE) ? 0 : max;
1507         mMatchConstraintPercentHeight = percent;
1508         if (percent > 0 && percent < 1
1509                 && mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD) {
1510             mMatchConstraintDefaultHeight = MATCH_CONSTRAINT_PERCENT;
1511         }
1512     }
1513 
1514     /**
1515      * Set the ratio of the widget
1516      *
1517      * @param ratio given string of format [H|V],[float|x:y] or [float|x:y]
1518      */
setDimensionRatio(String ratio)1519     public void setDimensionRatio(String ratio) {
1520         if (ratio == null || ratio.length() == 0) {
1521             mDimensionRatio = 0;
1522             return;
1523         }
1524         int dimensionRatioSide = UNKNOWN;
1525         float dimensionRatio = 0;
1526         int len = ratio.length();
1527         int commaIndex = ratio.indexOf(',');
1528         if (commaIndex > 0 && commaIndex < len - 1) {
1529             String dimension = ratio.substring(0, commaIndex);
1530             if (dimension.equalsIgnoreCase("W")) {
1531                 dimensionRatioSide = HORIZONTAL;
1532             } else if (dimension.equalsIgnoreCase("H")) {
1533                 dimensionRatioSide = VERTICAL;
1534             }
1535             commaIndex++;
1536         } else {
1537             commaIndex = 0;
1538         }
1539         int colonIndex = ratio.indexOf(':');
1540 
1541         if (colonIndex >= 0 && colonIndex < len - 1) {
1542             String nominator = ratio.substring(commaIndex, colonIndex);
1543             String denominator = ratio.substring(colonIndex + 1);
1544             if (nominator.length() > 0 && denominator.length() > 0) {
1545                 try {
1546                     float nominatorValue = Float.parseFloat(nominator);
1547                     float denominatorValue = Float.parseFloat(denominator);
1548                     if (nominatorValue > 0 && denominatorValue > 0) {
1549                         if (dimensionRatioSide == VERTICAL) {
1550                             dimensionRatio = Math.abs(denominatorValue / nominatorValue);
1551                         } else {
1552                             dimensionRatio = Math.abs(nominatorValue / denominatorValue);
1553                         }
1554                     }
1555                 } catch (NumberFormatException e) {
1556                     // Ignore
1557                 }
1558             }
1559         } else {
1560             String r = ratio.substring(commaIndex);
1561             if (r.length() > 0) {
1562                 try {
1563                     dimensionRatio = Float.parseFloat(r);
1564                 } catch (NumberFormatException e) {
1565                     // Ignore
1566                 }
1567             }
1568         }
1569 
1570         if (dimensionRatio > 0) {
1571             mDimensionRatio = dimensionRatio;
1572             mDimensionRatioSide = dimensionRatioSide;
1573         }
1574     }
1575 
1576     /**
1577      * Set the ratio of the widget
1578      * The ratio will be applied if at least one of the dimension
1579      * (width or height) is set to a behaviour
1580      * of DimensionBehaviour.MATCH_CONSTRAINT
1581      * -- the dimension's value will be set to the other dimension * ratio.
1582      *
1583      * @param ratio              A float value that describes W/H or H/W depending
1584      *                           on the provided dimensionRatioSide
1585      * @param dimensionRatioSide The side the ratio should be calculated on,
1586      *                           HORIZONTAL, VERTICAL, or UNKNOWN
1587      */
setDimensionRatio(float ratio, int dimensionRatioSide)1588     public void setDimensionRatio(float ratio, int dimensionRatioSide) {
1589         mDimensionRatio = ratio;
1590         mDimensionRatioSide = dimensionRatioSide;
1591     }
1592 
1593     /**
1594      * Return the current ratio of this widget
1595      *
1596      * @return the dimension ratio (HORIZONTAL, VERTICAL, or UNKNOWN)
1597      */
getDimensionRatio()1598     public float getDimensionRatio() {
1599         return mDimensionRatio;
1600     }
1601 
1602     /**
1603      * Return the current side on which ratio will be applied
1604      *
1605      * @return HORIZONTAL, VERTICAL, or UNKNOWN
1606      */
getDimensionRatioSide()1607     public int getDimensionRatioSide() {
1608         return mDimensionRatioSide;
1609     }
1610 
1611     /**
1612      * Set the horizontal bias percent to apply when we have two opposite constraints of
1613      * equal strength
1614      *
1615      * @param horizontalBiasPercent the percentage used
1616      */
setHorizontalBiasPercent(float horizontalBiasPercent)1617     public void setHorizontalBiasPercent(float horizontalBiasPercent) {
1618         mHorizontalBiasPercent = horizontalBiasPercent;
1619     }
1620 
1621     /**
1622      * Set the vertical bias percent to apply when we have two opposite constraints of
1623      * equal strength
1624      *
1625      * @param verticalBiasPercent the percentage used
1626      */
setVerticalBiasPercent(float verticalBiasPercent)1627     public void setVerticalBiasPercent(float verticalBiasPercent) {
1628         mVerticalBiasPercent = verticalBiasPercent;
1629     }
1630 
1631     /**
1632      * Set the minimum width of the widget
1633      *
1634      * @param w minimum width
1635      */
setMinWidth(int w)1636     public void setMinWidth(int w) {
1637         if (w < 0) {
1638             mMinWidth = 0;
1639         } else {
1640             mMinWidth = w;
1641         }
1642     }
1643 
1644     /**
1645      * Set the minimum height of the widget
1646      *
1647      * @param h minimum height
1648      */
setMinHeight(int h)1649     public void setMinHeight(int h) {
1650         if (h < 0) {
1651             mMinHeight = 0;
1652         } else {
1653             mMinHeight = h;
1654         }
1655     }
1656 
1657     /**
1658      * Set both width and height of the widget
1659      *
1660      * @param w width
1661      * @param h height
1662      */
setDimension(int w, int h)1663     public void setDimension(int w, int h) {
1664         mWidth = w;
1665         if (mWidth < mMinWidth) {
1666             mWidth = mMinWidth;
1667         }
1668         mHeight = h;
1669         if (mHeight < mMinHeight) {
1670             mHeight = mMinHeight;
1671         }
1672     }
1673 
1674     /**
1675      * Set the position+dimension of the widget given left/top/right/bottom
1676      *
1677      * @param left   left side position of the widget
1678      * @param top    top side position of the widget
1679      * @param right  right side position of the widget
1680      * @param bottom bottom side position of the widget
1681      */
setFrame(int left, int top, int right, int bottom)1682     public void setFrame(int left, int top, int right, int bottom) {
1683         int w = right - left;
1684         int h = bottom - top;
1685 
1686         mX = left;
1687         mY = top;
1688 
1689         if (mVisibility == ConstraintWidget.GONE) {
1690             mWidth = 0;
1691             mHeight = 0;
1692             return;
1693         }
1694 
1695         // correct dimensional instability caused by rounding errors
1696         if (mListDimensionBehaviors[DIMENSION_HORIZONTAL]
1697                 == DimensionBehaviour.FIXED && w < mWidth) {
1698             w = mWidth;
1699         }
1700         if (mListDimensionBehaviors[DIMENSION_VERTICAL]
1701                 == DimensionBehaviour.FIXED && h < mHeight) {
1702             h = mHeight;
1703         }
1704 
1705         mWidth = w;
1706         mHeight = h;
1707 
1708         if (mHeight < mMinHeight) {
1709             mHeight = mMinHeight;
1710         }
1711         if (mWidth < mMinWidth) {
1712             mWidth = mMinWidth;
1713         }
1714         if (mMatchConstraintMaxWidth > 0
1715                 && mListDimensionBehaviors[HORIZONTAL] == MATCH_CONSTRAINT) {
1716             mWidth = Math.min(mWidth, mMatchConstraintMaxWidth);
1717         }
1718         if (mMatchConstraintMaxHeight > 0
1719                 && mListDimensionBehaviors[VERTICAL] == MATCH_CONSTRAINT) {
1720             mHeight = Math.min(mHeight, mMatchConstraintMaxHeight);
1721         }
1722         if (w != mWidth) {
1723             mWidthOverride = mWidth;
1724         }
1725         if (h != mHeight) {
1726             mHeightOverride = mHeight;
1727         }
1728 
1729         if (LinearSystem.FULL_DEBUG) {
1730             System.out.println("update from solver " + mDebugName
1731                     + " " + mX + ":" + mY + " - " + mWidth + " x " + mHeight);
1732         }
1733     }
1734 
1735     /**
1736      * Set the position+dimension of the widget based on starting/ending positions on one dimension.
1737      *
1738      * @param start       Left/Top side position of the widget.
1739      * @param end         Right/Bottom side position of the widget.
1740      * @param orientation Orientation being set (HORIZONTAL/VERTICAL).
1741      */
setFrame(int start, int end, int orientation)1742     public void setFrame(int start, int end, int orientation) {
1743         if (orientation == HORIZONTAL) {
1744             setHorizontalDimension(start, end);
1745         } else if (orientation == VERTICAL) {
1746             setVerticalDimension(start, end);
1747         }
1748     }
1749 
1750     /**
1751      * Set the positions for the horizontal dimension only
1752      *
1753      * @param left  left side position of the widget
1754      * @param right right side position of the widget
1755      */
setHorizontalDimension(int left, int right)1756     public void setHorizontalDimension(int left, int right) {
1757         mX = left;
1758         mWidth = right - left;
1759         if (mWidth < mMinWidth) {
1760             mWidth = mMinWidth;
1761         }
1762     }
1763 
1764     /**
1765      * Set the positions for the vertical dimension only
1766      *
1767      * @param top    top side position of the widget
1768      * @param bottom bottom side position of the widget
1769      */
setVerticalDimension(int top, int bottom)1770     public void setVerticalDimension(int top, int bottom) {
1771         mY = top;
1772         mHeight = bottom - top;
1773         if (mHeight < mMinHeight) {
1774             mHeight = mMinHeight;
1775         }
1776     }
1777 
1778     /**
1779      * Get the left/top position of the widget relative to
1780      * the outer side of the container (right/bottom).
1781      *
1782      * @param orientation Orientation by which to find the relative positioning of the widget.
1783      * @return The relative position of the widget.
1784      */
getRelativePositioning(int orientation)1785     int getRelativePositioning(int orientation) {
1786         if (orientation == HORIZONTAL) {
1787             return mRelX;
1788         } else if (orientation == VERTICAL) {
1789             return mRelY;
1790         } else {
1791             return 0;
1792         }
1793     }
1794 
1795     /**
1796      * Set the left/top position of the widget relative to
1797      * the outer side of the container (right/bottom).
1798      *
1799      * @param offset      Offset of the relative position.
1800      * @param orientation Orientation of the offset being set.
1801      */
setRelativePositioning(int offset, int orientation)1802     void setRelativePositioning(int offset, int orientation) {
1803         if (orientation == HORIZONTAL) {
1804             mRelX = offset;
1805         } else if (orientation == VERTICAL) {
1806             mRelY = offset;
1807         }
1808     }
1809 
1810     /**
1811      * Set the baseline distance relative to the top of the widget
1812      *
1813      * @param baseline the distance of the baseline relative to the widget's top
1814      */
setBaselineDistance(int baseline)1815     public void setBaselineDistance(int baseline) {
1816         mBaselineDistance = baseline;
1817         mHasBaseline = baseline > 0;
1818     }
1819 
1820     /**
1821      * Set the companion widget. Typically, this would be the real widget we
1822      * represent with this instance of ConstraintWidget.
1823      */
setCompanionWidget(Object companion)1824     public void setCompanionWidget(Object companion) {
1825         mCompanionWidget = companion;
1826     }
1827 
1828     /**
1829      * Set the skip value for this widget. This can be used when a widget is in a container,
1830      * so that container can position the widget as if it was positioned further in the list
1831      * of widgets. For example, with Table, this is used to skip empty cells
1832      * (the widget after an empty cell will have a skip value of one)
1833      */
setContainerItemSkip(int skip)1834     public void setContainerItemSkip(int skip) {
1835         if (skip >= 0) {
1836             mContainerItemSkip = skip;
1837         } else {
1838             mContainerItemSkip = 0;
1839         }
1840     }
1841 
1842     /**
1843      * Accessor for the skip value
1844      *
1845      * @return skip value
1846      */
getContainerItemSkip()1847     public int getContainerItemSkip() {
1848         return mContainerItemSkip;
1849     }
1850 
1851     /**
1852      * Set the horizontal weight (only used in chains)
1853      *
1854      * @param horizontalWeight Floating point value weight
1855      */
setHorizontalWeight(float horizontalWeight)1856     public void setHorizontalWeight(float horizontalWeight) {
1857         mWeight[DIMENSION_HORIZONTAL] = horizontalWeight;
1858     }
1859 
1860     /**
1861      * Set the vertical weight (only used in chains)
1862      *
1863      * @param verticalWeight Floating point value weight
1864      */
setVerticalWeight(float verticalWeight)1865     public void setVerticalWeight(float verticalWeight) {
1866         mWeight[DIMENSION_VERTICAL] = verticalWeight;
1867     }
1868 
1869     /**
1870      * Set the chain starting from this widget to be packed.
1871      * The horizontal bias will control how elements of the chain are positioned.
1872      *
1873      * @param horizontalChainStyle (CHAIN_SPREAD, CHAIN_SPREAD_INSIDE, CHAIN_PACKED)
1874      */
setHorizontalChainStyle(int horizontalChainStyle)1875     public void setHorizontalChainStyle(int horizontalChainStyle) {
1876         mHorizontalChainStyle = horizontalChainStyle;
1877     }
1878 
1879     /**
1880      * get the chain starting from this widget to be packed.
1881      * The horizontal bias will control how elements of the chain are positioned.
1882      *
1883      * @return Horizontal Chain Style
1884      */
getHorizontalChainStyle()1885     public int getHorizontalChainStyle() {
1886         return mHorizontalChainStyle;
1887     }
1888 
1889     /**
1890      * Set the chain starting from this widget to be packed.
1891      * The vertical bias will control how elements of the chain are positioned.
1892      *
1893      * @param verticalChainStyle (CHAIN_SPREAD, CHAIN_SPREAD_INSIDE, CHAIN_PACKED)
1894      */
setVerticalChainStyle(int verticalChainStyle)1895     public void setVerticalChainStyle(int verticalChainStyle) {
1896         mVerticalChainStyle = verticalChainStyle;
1897     }
1898 
1899     /**
1900      * Set the chain starting from this widget to be packed.
1901      * The vertical bias will control how elements of the chain are positioned.
1902      */
getVerticalChainStyle()1903     public int getVerticalChainStyle() {
1904         return mVerticalChainStyle;
1905     }
1906 
1907     /**
1908      * Returns true if this widget should be used in a barrier
1909      */
allowedInBarrier()1910     public boolean allowedInBarrier() {
1911         return mVisibility != GONE;
1912     }
1913 
1914     /*-----------------------------------------------------------------------*/
1915     // Connections
1916     /*-----------------------------------------------------------------------*/
1917 
1918     /**
1919      * Immediate connection to an anchor without any checks.
1920      *
1921      * @param startType  The type of anchor on this widget
1922      * @param target     The target widget
1923      * @param endType    The type of anchor on the target widget
1924      * @param margin     How much margin we want to keep as
1925      *                   a minimum distance between the two anchors
1926      * @param goneMargin How much margin we want to keep if the target is set to {@code View.GONE}
1927      */
immediateConnect(ConstraintAnchor.Type startType, ConstraintWidget target, ConstraintAnchor.Type endType, int margin, int goneMargin)1928     public void immediateConnect(ConstraintAnchor.Type startType, ConstraintWidget target,
1929             ConstraintAnchor.Type endType, int margin, int goneMargin) {
1930         ConstraintAnchor startAnchor = getAnchor(startType);
1931         ConstraintAnchor endAnchor = target.getAnchor(endType);
1932         startAnchor.connect(endAnchor, margin, goneMargin, true);
1933     }
1934 
1935     /**
1936      * Connect the given anchors together (the from anchor should be owned by this widget)
1937      *
1938      * @param from   the anchor we are connecting from (of this widget)
1939      * @param to     the anchor we are connecting to
1940      * @param margin how much margin we want to have
1941      */
connect(ConstraintAnchor from, ConstraintAnchor to, int margin)1942     public void connect(ConstraintAnchor from, ConstraintAnchor to, int margin) {
1943         if (from.getOwner() == this) {
1944             connect(from.getType(), to.getOwner(), to.getType(), margin);
1945         }
1946     }
1947 
1948     /**
1949      * Connect a given anchor of this widget to another anchor of a target widget
1950      *
1951      * @param constraintFrom which anchor of this widget to connect from
1952      * @param target         the target widget
1953      * @param constraintTo   the target anchor on the target widget
1954      */
connect(ConstraintAnchor.Type constraintFrom, ConstraintWidget target, ConstraintAnchor.Type constraintTo)1955     public void connect(ConstraintAnchor.Type constraintFrom,
1956             ConstraintWidget target,
1957             ConstraintAnchor.Type constraintTo) {
1958         if (DEBUG) {
1959             System.out.println(this.getDebugName() + " connect "
1960                     + constraintFrom + " to " + target + " " + constraintTo);
1961         }
1962         connect(constraintFrom, target, constraintTo, 0);
1963     }
1964 
1965     /**
1966      * Connect a given anchor of this widget to another anchor of a target widget
1967      *
1968      * @param constraintFrom which anchor of this widget to connect from
1969      * @param target         the target widget
1970      * @param constraintTo   the target anchor on the target widget
1971      * @param margin         how much margin we want to keep as
1972      *                       a minimum distance between the two anchors
1973      */
connect(ConstraintAnchor.Type constraintFrom, ConstraintWidget target, ConstraintAnchor.Type constraintTo, int margin)1974     public void connect(ConstraintAnchor.Type constraintFrom,
1975             ConstraintWidget target,
1976             ConstraintAnchor.Type constraintTo, int margin) {
1977         if (constraintFrom == ConstraintAnchor.Type.CENTER) {
1978             // If we have center, we connect instead to the corresponding
1979             // left/right or top/bottom pairs
1980             if (constraintTo == ConstraintAnchor.Type.CENTER) {
1981                 ConstraintAnchor left = getAnchor(ConstraintAnchor.Type.LEFT);
1982                 ConstraintAnchor right = getAnchor(ConstraintAnchor.Type.RIGHT);
1983                 ConstraintAnchor top = getAnchor(ConstraintAnchor.Type.TOP);
1984                 ConstraintAnchor bottom = getAnchor(ConstraintAnchor.Type.BOTTOM);
1985                 boolean centerX = false;
1986                 boolean centerY = false;
1987                 if ((left != null && left.isConnected())
1988                         || (right != null && right.isConnected())) {
1989                     // don't apply center here
1990                 } else {
1991                     connect(ConstraintAnchor.Type.LEFT, target,
1992                             ConstraintAnchor.Type.LEFT, 0);
1993                     connect(ConstraintAnchor.Type.RIGHT, target,
1994                             ConstraintAnchor.Type.RIGHT, 0);
1995                     centerX = true;
1996                 }
1997                 if ((top != null && top.isConnected())
1998                         || (bottom != null && bottom.isConnected())) {
1999                     // don't apply center here
2000                 } else {
2001                     connect(ConstraintAnchor.Type.TOP, target,
2002                             ConstraintAnchor.Type.TOP, 0);
2003                     connect(ConstraintAnchor.Type.BOTTOM, target,
2004                             ConstraintAnchor.Type.BOTTOM, 0);
2005                     centerY = true;
2006                 }
2007                 if (centerX && centerY) {
2008                     ConstraintAnchor center = getAnchor(ConstraintAnchor.Type.CENTER);
2009                     center.connect(target.getAnchor(ConstraintAnchor.Type.CENTER), 0);
2010                 } else if (centerX) {
2011                     ConstraintAnchor center = getAnchor(ConstraintAnchor.Type.CENTER_X);
2012                     center.connect(target.getAnchor(ConstraintAnchor.Type.CENTER_X), 0);
2013                 } else if (centerY) {
2014                     ConstraintAnchor center = getAnchor(ConstraintAnchor.Type.CENTER_Y);
2015                     center.connect(target.getAnchor(ConstraintAnchor.Type.CENTER_Y), 0);
2016                 }
2017             } else if ((constraintTo == ConstraintAnchor.Type.LEFT)
2018                     || (constraintTo == ConstraintAnchor.Type.RIGHT)) {
2019                 connect(ConstraintAnchor.Type.LEFT, target,
2020                         constraintTo, 0);
2021                 connect(ConstraintAnchor.Type.RIGHT, target,
2022                         constraintTo, 0);
2023                 ConstraintAnchor center = getAnchor(ConstraintAnchor.Type.CENTER);
2024                 center.connect(target.getAnchor(constraintTo), 0);
2025             } else if ((constraintTo == ConstraintAnchor.Type.TOP)
2026                     || (constraintTo == ConstraintAnchor.Type.BOTTOM)) {
2027                 connect(ConstraintAnchor.Type.TOP, target,
2028                         constraintTo, 0);
2029                 connect(ConstraintAnchor.Type.BOTTOM, target,
2030                         constraintTo, 0);
2031                 ConstraintAnchor center = getAnchor(ConstraintAnchor.Type.CENTER);
2032                 center.connect(target.getAnchor(constraintTo), 0);
2033             }
2034         } else if (constraintFrom == ConstraintAnchor.Type.CENTER_X
2035                 && (constraintTo == ConstraintAnchor.Type.LEFT
2036                 || constraintTo == ConstraintAnchor.Type.RIGHT)) {
2037             ConstraintAnchor left = getAnchor(ConstraintAnchor.Type.LEFT);
2038             ConstraintAnchor targetAnchor = target.getAnchor(constraintTo);
2039             ConstraintAnchor right = getAnchor(ConstraintAnchor.Type.RIGHT);
2040             left.connect(targetAnchor, 0);
2041             right.connect(targetAnchor, 0);
2042             ConstraintAnchor centerX = getAnchor(ConstraintAnchor.Type.CENTER_X);
2043             centerX.connect(targetAnchor, 0);
2044         } else if (constraintFrom == ConstraintAnchor.Type.CENTER_Y
2045                 && (constraintTo == ConstraintAnchor.Type.TOP
2046                 || constraintTo == ConstraintAnchor.Type.BOTTOM)) {
2047             ConstraintAnchor targetAnchor = target.getAnchor(constraintTo);
2048             ConstraintAnchor top = getAnchor(ConstraintAnchor.Type.TOP);
2049             top.connect(targetAnchor, 0);
2050             ConstraintAnchor bottom = getAnchor(ConstraintAnchor.Type.BOTTOM);
2051             bottom.connect(targetAnchor, 0);
2052             ConstraintAnchor centerY = getAnchor(ConstraintAnchor.Type.CENTER_Y);
2053             centerY.connect(targetAnchor, 0);
2054         } else if (constraintFrom == ConstraintAnchor.Type.CENTER_X
2055                 && constraintTo == ConstraintAnchor.Type.CENTER_X) {
2056             // Center X connection will connect left & right
2057             ConstraintAnchor left = getAnchor(ConstraintAnchor.Type.LEFT);
2058             ConstraintAnchor leftTarget = target.getAnchor(ConstraintAnchor.Type.LEFT);
2059             left.connect(leftTarget, 0);
2060             ConstraintAnchor right = getAnchor(ConstraintAnchor.Type.RIGHT);
2061             ConstraintAnchor rightTarget = target.getAnchor(ConstraintAnchor.Type.RIGHT);
2062             right.connect(rightTarget, 0);
2063             ConstraintAnchor centerX = getAnchor(ConstraintAnchor.Type.CENTER_X);
2064             centerX.connect(target.getAnchor(constraintTo), 0);
2065         } else if (constraintFrom == ConstraintAnchor.Type.CENTER_Y
2066                 && constraintTo == ConstraintAnchor.Type.CENTER_Y) {
2067             // Center Y connection will connect top & bottom.
2068             ConstraintAnchor top = getAnchor(ConstraintAnchor.Type.TOP);
2069             ConstraintAnchor topTarget = target.getAnchor(ConstraintAnchor.Type.TOP);
2070             top.connect(topTarget, 0);
2071             ConstraintAnchor bottom = getAnchor(ConstraintAnchor.Type.BOTTOM);
2072             ConstraintAnchor bottomTarget = target.getAnchor(ConstraintAnchor.Type.BOTTOM);
2073             bottom.connect(bottomTarget, 0);
2074             ConstraintAnchor centerY = getAnchor(ConstraintAnchor.Type.CENTER_Y);
2075             centerY.connect(target.getAnchor(constraintTo), 0);
2076         } else {
2077             ConstraintAnchor fromAnchor = getAnchor(constraintFrom);
2078             ConstraintAnchor toAnchor = target.getAnchor(constraintTo);
2079             if (fromAnchor.isValidConnection(toAnchor)) {
2080                 // make sure that the baseline takes precedence over top/bottom
2081                 // and reversely, reset the baseline if we are connecting top/bottom
2082                 if (constraintFrom == ConstraintAnchor.Type.BASELINE) {
2083                     ConstraintAnchor top = getAnchor(ConstraintAnchor.Type.TOP);
2084                     ConstraintAnchor bottom = getAnchor(ConstraintAnchor.Type.BOTTOM);
2085                     if (top != null) {
2086                         top.reset();
2087                     }
2088                     if (bottom != null) {
2089                         bottom.reset();
2090                     }
2091                 } else if ((constraintFrom == ConstraintAnchor.Type.TOP)
2092                         || (constraintFrom == ConstraintAnchor.Type.BOTTOM)) {
2093                     ConstraintAnchor baseline = getAnchor(ConstraintAnchor.Type.BASELINE);
2094                     if (baseline != null) {
2095                         baseline.reset();
2096                     }
2097                     ConstraintAnchor center = getAnchor(ConstraintAnchor.Type.CENTER);
2098                     if (center.getTarget() != toAnchor) {
2099                         center.reset();
2100                     }
2101                     ConstraintAnchor opposite = getAnchor(constraintFrom).getOpposite();
2102                     ConstraintAnchor centerY = getAnchor(ConstraintAnchor.Type.CENTER_Y);
2103                     if (centerY.isConnected()) {
2104                         opposite.reset();
2105                         centerY.reset();
2106                     } else {
2107                         if (AUTOTAG_CENTER) {
2108                             // let's see if we need to mark center_y as connected
2109                             if (opposite.isConnected() && opposite.getTarget().getOwner()
2110                                     == toAnchor.getOwner()) {
2111                                 ConstraintAnchor targetCenterY = toAnchor.getOwner().getAnchor(
2112                                         ConstraintAnchor.Type.CENTER_Y);
2113                                 centerY.connect(targetCenterY, 0);
2114                             }
2115                         }
2116                     }
2117                 } else if ((constraintFrom == ConstraintAnchor.Type.LEFT)
2118                         || (constraintFrom == ConstraintAnchor.Type.RIGHT)) {
2119                     ConstraintAnchor center = getAnchor(ConstraintAnchor.Type.CENTER);
2120                     if (center.getTarget() != toAnchor) {
2121                         center.reset();
2122                     }
2123                     ConstraintAnchor opposite = getAnchor(constraintFrom).getOpposite();
2124                     ConstraintAnchor centerX = getAnchor(ConstraintAnchor.Type.CENTER_X);
2125                     if (centerX.isConnected()) {
2126                         opposite.reset();
2127                         centerX.reset();
2128                     } else {
2129                         if (AUTOTAG_CENTER) {
2130                             // let's see if we need to mark center_x as connected
2131                             if (opposite.isConnected() && opposite.getTarget().getOwner()
2132                                     == toAnchor.getOwner()) {
2133                                 ConstraintAnchor targetCenterX = toAnchor.getOwner().getAnchor(
2134                                         ConstraintAnchor.Type.CENTER_X);
2135                                 centerX.connect(targetCenterX, 0);
2136                             }
2137                         }
2138                     }
2139 
2140                 }
2141                 fromAnchor.connect(toAnchor, margin);
2142             }
2143         }
2144     }
2145 
2146     /**
2147      * Reset all the constraints set on this widget
2148      */
resetAllConstraints()2149     public void resetAllConstraints() {
2150         resetAnchors();
2151         setVerticalBiasPercent(DEFAULT_BIAS);
2152         setHorizontalBiasPercent(DEFAULT_BIAS);
2153     }
2154 
2155     /**
2156      * Reset the given anchor
2157      *
2158      * @param anchor the anchor we want to reset
2159      */
resetAnchor(ConstraintAnchor anchor)2160     public void resetAnchor(ConstraintAnchor anchor) {
2161         if (getParent() != null) {
2162             if (getParent() instanceof ConstraintWidgetContainer) {
2163                 ConstraintWidgetContainer parent = (ConstraintWidgetContainer) getParent();
2164                 if (parent.handlesInternalConstraints()) {
2165                     return;
2166                 }
2167             }
2168         }
2169         ConstraintAnchor left = getAnchor(ConstraintAnchor.Type.LEFT);
2170         ConstraintAnchor right = getAnchor(ConstraintAnchor.Type.RIGHT);
2171         ConstraintAnchor top = getAnchor(ConstraintAnchor.Type.TOP);
2172         ConstraintAnchor bottom = getAnchor(ConstraintAnchor.Type.BOTTOM);
2173         ConstraintAnchor center = getAnchor(ConstraintAnchor.Type.CENTER);
2174         ConstraintAnchor centerX = getAnchor(ConstraintAnchor.Type.CENTER_X);
2175         ConstraintAnchor centerY = getAnchor(ConstraintAnchor.Type.CENTER_Y);
2176 
2177         if (anchor == center) {
2178             if (left.isConnected() && right.isConnected()
2179                     && left.getTarget() == right.getTarget()) {
2180                 left.reset();
2181                 right.reset();
2182             }
2183             if (top.isConnected() && bottom.isConnected()
2184                     && top.getTarget() == bottom.getTarget()) {
2185                 top.reset();
2186                 bottom.reset();
2187             }
2188             mHorizontalBiasPercent = 0.5f;
2189             mVerticalBiasPercent = 0.5f;
2190         } else if (anchor == centerX) {
2191             if (left.isConnected() && right.isConnected()
2192                     && left.getTarget().getOwner() == right.getTarget().getOwner()) {
2193                 left.reset();
2194                 right.reset();
2195             }
2196             mHorizontalBiasPercent = 0.5f;
2197         } else if (anchor == centerY) {
2198             if (top.isConnected() && bottom.isConnected()
2199                     && top.getTarget().getOwner() == bottom.getTarget().getOwner()) {
2200                 top.reset();
2201                 bottom.reset();
2202             }
2203             mVerticalBiasPercent = 0.5f;
2204         } else if (anchor == left || anchor == right) {
2205             if (left.isConnected() && left.getTarget() == right.getTarget()) {
2206                 center.reset();
2207             }
2208         } else if (anchor == top || anchor == bottom) {
2209             if (top.isConnected() && top.getTarget() == bottom.getTarget()) {
2210                 center.reset();
2211             }
2212         }
2213         anchor.reset();
2214     }
2215 
2216     /**
2217      * Reset all connections
2218      */
resetAnchors()2219     public void resetAnchors() {
2220         ConstraintWidget parent = getParent();
2221         if (parent != null && parent instanceof ConstraintWidgetContainer) {
2222             ConstraintWidgetContainer parentContainer = (ConstraintWidgetContainer) getParent();
2223             if (parentContainer.handlesInternalConstraints()) {
2224                 return;
2225             }
2226         }
2227         for (int i = 0, mAnchorsSize = mAnchors.size(); i < mAnchorsSize; i++) {
2228             final ConstraintAnchor anchor = mAnchors.get(i);
2229             anchor.reset();
2230         }
2231     }
2232 
2233     /**
2234      * Given a type of anchor, returns the corresponding anchor.
2235      *
2236      * @param anchorType type of the anchor (LEFT, TOP, RIGHT, BOTTOM, BASELINE, CENTER_X, CENTER_Y)
2237      * @return the matching anchor
2238      */
getAnchor(ConstraintAnchor.Type anchorType)2239     public ConstraintAnchor getAnchor(ConstraintAnchor.Type anchorType) {
2240         switch (anchorType) {
2241             case LEFT: {
2242                 return mLeft;
2243             }
2244             case TOP: {
2245                 return mTop;
2246             }
2247             case RIGHT: {
2248                 return mRight;
2249             }
2250             case BOTTOM: {
2251                 return mBottom;
2252             }
2253             case BASELINE: {
2254                 return mBaseline;
2255             }
2256             case CENTER_X: {
2257                 return mCenterX;
2258             }
2259             case CENTER_Y: {
2260                 return mCenterY;
2261             }
2262             case CENTER: {
2263                 return mCenter;
2264             }
2265             case NONE:
2266                 return null;
2267         }
2268         throw new AssertionError(anchorType.name());
2269     }
2270 
2271     /**
2272      * Accessor for the horizontal dimension behaviour
2273      *
2274      * @return dimension behaviour
2275      */
getHorizontalDimensionBehaviour()2276     public DimensionBehaviour getHorizontalDimensionBehaviour() {
2277         return mListDimensionBehaviors[DIMENSION_HORIZONTAL];
2278     }
2279 
2280     /**
2281      * Accessor for the vertical dimension behaviour
2282      *
2283      * @return dimension behaviour
2284      */
getVerticalDimensionBehaviour()2285     public DimensionBehaviour getVerticalDimensionBehaviour() {
2286         return mListDimensionBehaviors[DIMENSION_VERTICAL];
2287     }
2288 
2289     /**
2290      * Get the widget's {@link DimensionBehaviour} in an specific orientation.
2291      *
2292      * @return The {@link DimensionBehaviour} of the widget.
2293      */
getDimensionBehaviour(int orientation)2294     public DimensionBehaviour getDimensionBehaviour(int orientation) {
2295         if (orientation == HORIZONTAL) {
2296             return getHorizontalDimensionBehaviour();
2297         } else if (orientation == VERTICAL) {
2298             return getVerticalDimensionBehaviour();
2299         } else {
2300             return null;
2301         }
2302     }
2303 
2304     /**
2305      * Set the widget's behaviour for the horizontal dimension
2306      *
2307      * @param behaviour the horizontal dimension's behaviour
2308      */
setHorizontalDimensionBehaviour(DimensionBehaviour behaviour)2309     public void setHorizontalDimensionBehaviour(DimensionBehaviour behaviour) {
2310         mListDimensionBehaviors[DIMENSION_HORIZONTAL] = behaviour;
2311     }
2312 
2313     /**
2314      * Set the widget's behaviour for the vertical dimension
2315      *
2316      * @param behaviour the vertical dimension's behaviour
2317      */
setVerticalDimensionBehaviour(DimensionBehaviour behaviour)2318     public void setVerticalDimensionBehaviour(DimensionBehaviour behaviour) {
2319         mListDimensionBehaviors[DIMENSION_VERTICAL] = behaviour;
2320     }
2321 
2322     /**
2323      * Test if you are in a Horizontal chain
2324      *
2325      * @return true if in a horizontal chain
2326      */
isInHorizontalChain()2327     public boolean isInHorizontalChain() {
2328         if ((mLeft.mTarget != null && mLeft.mTarget.mTarget == mLeft)
2329                 || (mRight.mTarget != null && mRight.mTarget.mTarget == mRight)) {
2330             return true;
2331         }
2332         return false;
2333     }
2334 
2335     /**
2336      * Return the previous chain member if one exists
2337      *
2338      * @param orientation HORIZONTAL or VERTICAL
2339      * @return the previous chain member or null if we are the first chain element
2340      */
getPreviousChainMember(int orientation)2341     public ConstraintWidget getPreviousChainMember(int orientation) {
2342         if (orientation == HORIZONTAL) {
2343             if (mLeft.mTarget != null && mLeft.mTarget.mTarget == mLeft) {
2344                 return mLeft.mTarget.mOwner;
2345             }
2346         } else if (orientation == VERTICAL) {
2347             if (mTop.mTarget != null && mTop.mTarget.mTarget == mTop) {
2348                 return mTop.mTarget.mOwner;
2349             }
2350         }
2351         return null;
2352     }
2353 
2354     /**
2355      * Return the next chain member if one exists
2356      *
2357      * @param orientation HORIZONTAL or VERTICAL
2358      * @return the next chain member or null if we are the last chain element
2359      */
getNextChainMember(int orientation)2360     public ConstraintWidget getNextChainMember(int orientation) {
2361         if (orientation == HORIZONTAL) {
2362             if (mRight.mTarget != null && mRight.mTarget.mTarget == mRight) {
2363                 return mRight.mTarget.mOwner;
2364             }
2365         } else if (orientation == VERTICAL) {
2366             if (mBottom.mTarget != null && mBottom.mTarget.mTarget == mBottom) {
2367                 return mBottom.mTarget.mOwner;
2368             }
2369         }
2370         return null;
2371     }
2372 
2373     /**
2374      * if in a horizontal chain return the left most widget in the chain.
2375      *
2376      * @return left most widget in chain or null
2377      */
getHorizontalChainControlWidget()2378     public ConstraintWidget getHorizontalChainControlWidget() {
2379         ConstraintWidget found = null;
2380         if (isInHorizontalChain()) {
2381             ConstraintWidget tmp = this;
2382 
2383             while (found == null && tmp != null) {
2384                 ConstraintAnchor anchor = tmp.getAnchor(ConstraintAnchor.Type.LEFT);
2385                 ConstraintAnchor targetOwner = (anchor == null) ? null : anchor.getTarget();
2386                 ConstraintWidget target = (targetOwner == null) ? null : targetOwner.getOwner();
2387                 if (target == getParent()) {
2388                     found = tmp;
2389                     break;
2390                 }
2391                 ConstraintAnchor targetAnchor = (target == null)
2392                         ? null : target.getAnchor(ConstraintAnchor.Type.RIGHT).getTarget();
2393                 if (targetAnchor != null && targetAnchor.getOwner() != tmp) {
2394                     found = tmp;
2395                 } else {
2396                     tmp = target;
2397                 }
2398             }
2399         }
2400         return found;
2401     }
2402 
2403 
2404     /**
2405      * Test if you are in a vertical chain
2406      *
2407      * @return true if in a vertical chain
2408      */
isInVerticalChain()2409     public boolean isInVerticalChain() {
2410         if ((mTop.mTarget != null && mTop.mTarget.mTarget == mTop)
2411                 || (mBottom.mTarget != null && mBottom.mTarget.mTarget == mBottom)) {
2412             return true;
2413         }
2414         return false;
2415     }
2416 
2417     /**
2418      * if in a vertical chain return the top most widget in the chain.
2419      *
2420      * @return top most widget in chain or null
2421      */
getVerticalChainControlWidget()2422     public ConstraintWidget getVerticalChainControlWidget() {
2423         ConstraintWidget found = null;
2424         if (isInVerticalChain()) {
2425             ConstraintWidget tmp = this;
2426             while (found == null && tmp != null) {
2427                 ConstraintAnchor anchor = tmp.getAnchor(ConstraintAnchor.Type.TOP);
2428                 ConstraintAnchor targetOwner = (anchor == null) ? null : anchor.getTarget();
2429                 ConstraintWidget target = (targetOwner == null) ? null : targetOwner.getOwner();
2430                 if (target == getParent()) {
2431                     found = tmp;
2432                     break;
2433                 }
2434                 ConstraintAnchor targetAnchor = (target == null)
2435                         ? null : target.getAnchor(ConstraintAnchor.Type.BOTTOM).getTarget();
2436                 if (targetAnchor != null && targetAnchor.getOwner() != tmp) {
2437                     found = tmp;
2438                 } else {
2439                     tmp = target;
2440                 }
2441             }
2442 
2443         }
2444         return found;
2445     }
2446 
2447     /**
2448      * Determine if the widget is the first element of a chain in a given orientation.
2449      *
2450      * @param orientation Either {@link #HORIZONTAL} or {@link #VERTICAL}
2451      * @return if the widget is the head of a chain
2452      */
isChainHead(int orientation)2453     private boolean isChainHead(int orientation) {
2454         int offset = orientation * 2;
2455         return (mListAnchors[offset].mTarget != null
2456                 && mListAnchors[offset].mTarget.mTarget != mListAnchors[offset])
2457                 && (mListAnchors[offset + 1].mTarget != null
2458                 && mListAnchors[offset + 1].mTarget.mTarget == mListAnchors[offset + 1]);
2459     }
2460 
2461 
2462     /*-----------------------------------------------------------------------*/
2463     // Constraints
2464     /*-----------------------------------------------------------------------*/
2465 
2466     /**
2467      * Add this widget to the solver
2468      *
2469      * @param system   the solver we want to add the widget to
2470      * @param optimize true if {@link Optimizer#OPTIMIZATION_GRAPH} is on
2471      */
addToSolver(LinearSystem system, boolean optimize)2472     public void addToSolver(LinearSystem system, boolean optimize) {
2473         if (LinearSystem.FULL_DEBUG) {
2474             System.out.println("\n----------------------------------------------");
2475             System.out.println("-- adding " + getDebugName() + " to the solver");
2476             if (isInVirtualLayout()) {
2477                 System.out.println("-- note: is in virtual layout");
2478             }
2479             System.out.println("----------------------------------------------\n");
2480         }
2481 
2482         SolverVariable left = system.createObjectVariable(mLeft);
2483         SolverVariable right = system.createObjectVariable(mRight);
2484         SolverVariable top = system.createObjectVariable(mTop);
2485         SolverVariable bottom = system.createObjectVariable(mBottom);
2486         SolverVariable baseline = system.createObjectVariable(mBaseline);
2487 
2488         boolean horizontalParentWrapContent = false;
2489         boolean verticalParentWrapContent = false;
2490         if (mParent != null) {
2491             horizontalParentWrapContent = mParent != null
2492                     ? mParent.mListDimensionBehaviors[DIMENSION_HORIZONTAL] == WRAP_CONTENT : false;
2493             verticalParentWrapContent = mParent != null
2494                     ? mParent.mListDimensionBehaviors[DIMENSION_VERTICAL] == WRAP_CONTENT : false;
2495 
2496             switch (mWrapBehaviorInParent) {
2497                 case WRAP_BEHAVIOR_SKIPPED: {
2498                     horizontalParentWrapContent = false;
2499                     verticalParentWrapContent = false;
2500                 }
2501                 break;
2502                 case WRAP_BEHAVIOR_HORIZONTAL_ONLY: {
2503                     verticalParentWrapContent = false;
2504                 }
2505                 break;
2506                 case WRAP_BEHAVIOR_VERTICAL_ONLY: {
2507                     horizontalParentWrapContent = false;
2508                 }
2509                 break;
2510             }
2511         }
2512 
2513         if (!(mVisibility != GONE || mAnimated || hasDependencies()
2514                 || mIsInBarrier[HORIZONTAL] || mIsInBarrier[VERTICAL])) {
2515             return;
2516         }
2517 
2518         if (mResolvedHorizontal || mResolvedVertical) {
2519             if (LinearSystem.FULL_DEBUG) {
2520                 System.out.println("\n----------------------------------------------");
2521                 System.out.println("-- setting " + getDebugName()
2522                         + " to " + mX + ", " + mY + " " + mWidth + " x " + mHeight);
2523                 System.out.println("----------------------------------------------\n");
2524             }
2525             // For now apply all, but that won't work for wrap/wrap layouts.
2526             if (mResolvedHorizontal) {
2527                 system.addEquality(left, mX);
2528                 system.addEquality(right, mX + mWidth);
2529                 if (horizontalParentWrapContent && mParent != null) {
2530                     if (mOptimizeWrapOnResolved) {
2531                         ConstraintWidgetContainer container = (ConstraintWidgetContainer) mParent;
2532                         container.addHorizontalWrapMinVariable(mLeft);
2533                         container.addHorizontalWrapMaxVariable(mRight);
2534                     } else {
2535                         int wrapStrength = SolverVariable.STRENGTH_EQUALITY;
2536                         system.addGreaterThan(system.createObjectVariable(mParent.mRight),
2537                                 right, 0, wrapStrength);
2538                     }
2539                 }
2540             }
2541             if (mResolvedVertical) {
2542                 system.addEquality(top, mY);
2543                 system.addEquality(bottom, mY + mHeight);
2544                 if (mBaseline.hasDependents()) {
2545                     system.addEquality(baseline, mY + mBaselineDistance);
2546                 }
2547                 if (verticalParentWrapContent && mParent != null) {
2548                     if (mOptimizeWrapOnResolved) {
2549                         ConstraintWidgetContainer container = (ConstraintWidgetContainer) mParent;
2550                         container.addVerticalWrapMinVariable(mTop);
2551                         container.addVerticalWrapMaxVariable(mBottom);
2552                     } else {
2553                         int wrapStrength = SolverVariable.STRENGTH_EQUALITY;
2554                         system.addGreaterThan(system.createObjectVariable(mParent.mBottom),
2555                                 bottom, 0, wrapStrength);
2556                     }
2557                 }
2558             }
2559             if (mResolvedHorizontal && mResolvedVertical) {
2560                 mResolvedHorizontal = false;
2561                 mResolvedVertical = false;
2562                 if (LinearSystem.FULL_DEBUG) {
2563                     System.out.println("\n----------------------------------------------");
2564                     System.out.println("-- setting COMPLETED for " + getDebugName());
2565                     System.out.println("----------------------------------------------\n");
2566                 }
2567                 return;
2568             }
2569         }
2570 
2571         if (LinearSystem.sMetrics != null) {
2572             LinearSystem.sMetrics.widgets++;
2573         }
2574         if (FULL_DEBUG) {
2575             if (optimize && mHorizontalRun != null && mVerticalRun != null) {
2576                 System.out.println("-- horizontal run : "
2577                         + mHorizontalRun.start + " : " + mHorizontalRun.end);
2578                 System.out.println("-- vertical run : "
2579                         + mVerticalRun.start + " : " + mVerticalRun.end);
2580             }
2581         }
2582         if (optimize && mHorizontalRun != null && mVerticalRun != null
2583                 && mHorizontalRun.start.resolved && mHorizontalRun.end.resolved
2584                 && mVerticalRun.start.resolved && mVerticalRun.end.resolved) {
2585 
2586             if (LinearSystem.sMetrics != null) {
2587                 LinearSystem.sMetrics.graphSolved++;
2588             }
2589             system.addEquality(left, mHorizontalRun.start.value);
2590             system.addEquality(right, mHorizontalRun.end.value);
2591             system.addEquality(top, mVerticalRun.start.value);
2592             system.addEquality(bottom, mVerticalRun.end.value);
2593             system.addEquality(baseline, mVerticalRun.baseline.value);
2594             if (mParent != null) {
2595                 if (horizontalParentWrapContent
2596                         && isTerminalWidget[HORIZONTAL] && !isInHorizontalChain()) {
2597                     SolverVariable parentMax = system.createObjectVariable(mParent.mRight);
2598                     system.addGreaterThan(parentMax, right, 0, SolverVariable.STRENGTH_FIXED);
2599                 }
2600                 if (verticalParentWrapContent
2601                         && isTerminalWidget[VERTICAL] && !isInVerticalChain()) {
2602                     SolverVariable parentMax = system.createObjectVariable(mParent.mBottom);
2603                     system.addGreaterThan(parentMax, bottom, 0, SolverVariable.STRENGTH_FIXED);
2604                 }
2605             }
2606             mResolvedHorizontal = false;
2607             mResolvedVertical = false;
2608             return; // we are done here
2609         }
2610         if (LinearSystem.sMetrics != null) {
2611             LinearSystem.sMetrics.linearSolved++;
2612         }
2613 
2614         boolean inHorizontalChain = false;
2615         boolean inVerticalChain = false;
2616 
2617         if (mParent != null) {
2618             // Add this widget to a horizontal chain if it is the Head of it.
2619             if (isChainHead(HORIZONTAL)) {
2620                 ((ConstraintWidgetContainer) mParent).addChain(this, HORIZONTAL);
2621                 inHorizontalChain = true;
2622             } else {
2623                 inHorizontalChain = isInHorizontalChain();
2624             }
2625 
2626             // Add this widget to a vertical chain if it is the Head of it.
2627             if (isChainHead(VERTICAL)) {
2628                 ((ConstraintWidgetContainer) mParent).addChain(this, VERTICAL);
2629                 inVerticalChain = true;
2630             } else {
2631                 inVerticalChain = isInVerticalChain();
2632             }
2633 
2634             if (!inHorizontalChain && horizontalParentWrapContent && mVisibility != GONE
2635                     && mLeft.mTarget == null && mRight.mTarget == null) {
2636                 if (FULL_DEBUG) {
2637                     System.out.println("<>1 ADDING H WRAP GREATER FOR " + getDebugName());
2638                 }
2639                 SolverVariable parentRight = system.createObjectVariable(mParent.mRight);
2640                 system.addGreaterThan(parentRight, right, 0, SolverVariable.STRENGTH_LOW);
2641             }
2642 
2643             if (!inVerticalChain && verticalParentWrapContent && mVisibility != GONE
2644                     && mTop.mTarget == null && mBottom.mTarget == null && mBaseline == null) {
2645                 if (FULL_DEBUG) {
2646                     System.out.println("<>1 ADDING V WRAP GREATER FOR " + getDebugName());
2647                 }
2648                 SolverVariable parentBottom = system.createObjectVariable(mParent.mBottom);
2649                 system.addGreaterThan(parentBottom, bottom, 0, SolverVariable.STRENGTH_LOW);
2650             }
2651         }
2652 
2653         int width = mWidth;
2654         if (width < mMinWidth) {
2655             width = mMinWidth;
2656         }
2657         int height = mHeight;
2658         if (height < mMinHeight) {
2659             height = mMinHeight;
2660         }
2661 
2662         // Dimensions can be either fixed (a given value)
2663         // or dependent on the solver if set to MATCH_CONSTRAINT
2664         boolean horizontalDimensionFixed =
2665                 mListDimensionBehaviors[DIMENSION_HORIZONTAL] != MATCH_CONSTRAINT;
2666         boolean verticalDimensionFixed =
2667                 mListDimensionBehaviors[DIMENSION_VERTICAL] != MATCH_CONSTRAINT;
2668 
2669         // We evaluate the dimension ratio here as the connections can change.
2670         // TODO: have a validation pass after connection instead
2671         boolean useRatio = false;
2672         mResolvedDimensionRatioSide = mDimensionRatioSide;
2673         mResolvedDimensionRatio = mDimensionRatio;
2674 
2675         int matchConstraintDefaultWidth = mMatchConstraintDefaultWidth;
2676         int matchConstraintDefaultHeight = mMatchConstraintDefaultHeight;
2677 
2678         if (mDimensionRatio > 0 && mVisibility != GONE) {
2679             useRatio = true;
2680             if (mListDimensionBehaviors[DIMENSION_HORIZONTAL] == MATCH_CONSTRAINT
2681                     && matchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD) {
2682                 matchConstraintDefaultWidth = MATCH_CONSTRAINT_RATIO;
2683             }
2684             if (mListDimensionBehaviors[DIMENSION_VERTICAL] == MATCH_CONSTRAINT
2685                     && matchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD) {
2686                 matchConstraintDefaultHeight = MATCH_CONSTRAINT_RATIO;
2687             }
2688 
2689             if (mListDimensionBehaviors[DIMENSION_HORIZONTAL] == MATCH_CONSTRAINT
2690                     && mListDimensionBehaviors[DIMENSION_VERTICAL] == MATCH_CONSTRAINT
2691                     && matchConstraintDefaultWidth == MATCH_CONSTRAINT_RATIO
2692                     && matchConstraintDefaultHeight == MATCH_CONSTRAINT_RATIO) {
2693                 setupDimensionRatio(horizontalParentWrapContent, verticalParentWrapContent,
2694                         horizontalDimensionFixed, verticalDimensionFixed);
2695             } else if (mListDimensionBehaviors[DIMENSION_HORIZONTAL] == MATCH_CONSTRAINT
2696                     && matchConstraintDefaultWidth == MATCH_CONSTRAINT_RATIO) {
2697                 mResolvedDimensionRatioSide = HORIZONTAL;
2698                 width = (int) (mResolvedDimensionRatio * mHeight);
2699                 if (mListDimensionBehaviors[DIMENSION_VERTICAL] != MATCH_CONSTRAINT) {
2700                     matchConstraintDefaultWidth = MATCH_CONSTRAINT_RATIO_RESOLVED;
2701                     useRatio = false;
2702                 }
2703             } else if (mListDimensionBehaviors[DIMENSION_VERTICAL] == MATCH_CONSTRAINT
2704                     && matchConstraintDefaultHeight == MATCH_CONSTRAINT_RATIO) {
2705                 mResolvedDimensionRatioSide = VERTICAL;
2706                 if (mDimensionRatioSide == UNKNOWN) {
2707                     // need to reverse the ratio as the parsing is done in horizontal mode
2708                     mResolvedDimensionRatio = 1 / mResolvedDimensionRatio;
2709                 }
2710                 height = (int) (mResolvedDimensionRatio * mWidth);
2711                 if (mListDimensionBehaviors[DIMENSION_HORIZONTAL] != MATCH_CONSTRAINT) {
2712                     matchConstraintDefaultHeight = MATCH_CONSTRAINT_RATIO_RESOLVED;
2713                     useRatio = false;
2714                 }
2715             }
2716         }
2717 
2718         mResolvedMatchConstraintDefault[HORIZONTAL] = matchConstraintDefaultWidth;
2719         mResolvedMatchConstraintDefault[VERTICAL] = matchConstraintDefaultHeight;
2720         mResolvedHasRatio = useRatio;
2721 
2722         boolean useHorizontalRatio = useRatio && (mResolvedDimensionRatioSide == HORIZONTAL
2723                 || mResolvedDimensionRatioSide == UNKNOWN);
2724 
2725         boolean useVerticalRatio = useRatio && (mResolvedDimensionRatioSide == VERTICAL
2726                 || mResolvedDimensionRatioSide == UNKNOWN);
2727 
2728         // Horizontal resolution
2729         boolean wrapContent = (mListDimensionBehaviors[DIMENSION_HORIZONTAL] == WRAP_CONTENT)
2730                 && (this instanceof ConstraintWidgetContainer);
2731         if (wrapContent) {
2732             width = 0;
2733         }
2734 
2735         boolean applyPosition = true;
2736         if (mCenter.isConnected()) {
2737             applyPosition = false;
2738         }
2739 
2740         boolean isInHorizontalBarrier = mIsInBarrier[HORIZONTAL];
2741         boolean isInVerticalBarrier = mIsInBarrier[VERTICAL];
2742 
2743         if (mHorizontalResolution != DIRECT && !mResolvedHorizontal) {
2744             if (!optimize || !(mHorizontalRun != null
2745                     && mHorizontalRun.start.resolved && mHorizontalRun.end.resolved)) {
2746                 SolverVariable parentMax = mParent != null
2747                         ? system.createObjectVariable(mParent.mRight) : null;
2748                 SolverVariable parentMin = mParent != null
2749                         ? system.createObjectVariable(mParent.mLeft) : null;
2750                 applyConstraints(system, true, horizontalParentWrapContent,
2751                         verticalParentWrapContent, isTerminalWidget[HORIZONTAL], parentMin,
2752                         parentMax, mListDimensionBehaviors[DIMENSION_HORIZONTAL], wrapContent,
2753                         mLeft, mRight, mX, width,
2754                         mMinWidth, mMaxDimension[HORIZONTAL],
2755                         mHorizontalBiasPercent, useHorizontalRatio,
2756                         mListDimensionBehaviors[VERTICAL] == MATCH_CONSTRAINT,
2757                         inHorizontalChain, inVerticalChain, isInHorizontalBarrier,
2758                         matchConstraintDefaultWidth, matchConstraintDefaultHeight,
2759                         mMatchConstraintMinWidth, mMatchConstraintMaxWidth,
2760                         mMatchConstraintPercentWidth, applyPosition);
2761             } else if (optimize) {
2762                 system.addEquality(left, mHorizontalRun.start.value);
2763                 system.addEquality(right, mHorizontalRun.end.value);
2764                 if (mParent != null) {
2765                     if (horizontalParentWrapContent
2766                             && isTerminalWidget[HORIZONTAL] && !isInHorizontalChain()) {
2767                         if (FULL_DEBUG) {
2768                             System.out.println("<>2 ADDING H WRAP GREATER FOR " + getDebugName());
2769                         }
2770                         SolverVariable parentMax = system.createObjectVariable(mParent.mRight);
2771                         system.addGreaterThan(parentMax, right, 0, SolverVariable.STRENGTH_FIXED);
2772                     }
2773                 }
2774             }
2775         }
2776 
2777         boolean applyVerticalConstraints = true;
2778         if (optimize && mVerticalRun != null
2779                 && mVerticalRun.start.resolved && mVerticalRun.end.resolved) {
2780             system.addEquality(top, mVerticalRun.start.value);
2781             system.addEquality(bottom, mVerticalRun.end.value);
2782             system.addEquality(baseline, mVerticalRun.baseline.value);
2783             if (mParent != null) {
2784                 if (!inVerticalChain && verticalParentWrapContent && isTerminalWidget[VERTICAL]) {
2785                     if (FULL_DEBUG) {
2786                         System.out.println("<>2 ADDING V WRAP GREATER FOR " + getDebugName());
2787                     }
2788                     SolverVariable parentMax = system.createObjectVariable(mParent.mBottom);
2789                     system.addGreaterThan(parentMax, bottom, 0, SolverVariable.STRENGTH_FIXED);
2790                 }
2791             }
2792             applyVerticalConstraints = false;
2793         }
2794         if (mVerticalResolution == DIRECT) {
2795             if (LinearSystem.FULL_DEBUG) {
2796                 System.out.println("\n----------------------------------------------");
2797                 System.out.println("-- DONE adding " + getDebugName() + " to the solver");
2798                 System.out.println("-- SKIP VERTICAL RESOLUTION");
2799                 System.out.println("----------------------------------------------\n");
2800             }
2801             applyVerticalConstraints = false;
2802         }
2803         if (applyVerticalConstraints && !mResolvedVertical) {
2804             // Vertical Resolution
2805             wrapContent = (mListDimensionBehaviors[DIMENSION_VERTICAL] == WRAP_CONTENT)
2806                     && (this instanceof ConstraintWidgetContainer);
2807             if (wrapContent) {
2808                 height = 0;
2809             }
2810 
2811             SolverVariable parentMax = mParent != null
2812                     ? system.createObjectVariable(mParent.mBottom) : null;
2813             SolverVariable parentMin = mParent != null
2814                     ? system.createObjectVariable(mParent.mTop) : null;
2815 
2816             if (mBaselineDistance > 0 || mVisibility == GONE) {
2817                 // if we are GONE we might still have to deal with baseline,
2818                 // even if our baseline distance would be zero
2819                 if (mBaseline.mTarget != null) {
2820                     system.addEquality(baseline, top, getBaselineDistance(),
2821                             SolverVariable.STRENGTH_FIXED);
2822                     SolverVariable baselineTarget = system.createObjectVariable(mBaseline.mTarget);
2823                     int baselineMargin = mBaseline.getMargin();
2824                     system.addEquality(baseline, baselineTarget, baselineMargin,
2825                             SolverVariable.STRENGTH_FIXED);
2826                     applyPosition = false;
2827                     if (verticalParentWrapContent) {
2828                         if (FULL_DEBUG) {
2829                             System.out.println("<>3 ADDING V WRAP GREATER FOR " + getDebugName());
2830                         }
2831                         SolverVariable end = system.createObjectVariable(mBottom);
2832                         int wrapStrength = SolverVariable.STRENGTH_EQUALITY;
2833                         system.addGreaterThan(parentMax, end, 0, wrapStrength);
2834                     }
2835                 } else if (mVisibility == GONE) {
2836                     // TODO: use the constraints graph here to help
2837                     system.addEquality(baseline, top, mBaseline.getMargin(),
2838                             SolverVariable.STRENGTH_FIXED);
2839                 } else {
2840                     system.addEquality(baseline, top, getBaselineDistance(),
2841                             SolverVariable.STRENGTH_FIXED);
2842                 }
2843             }
2844 
2845             applyConstraints(system, false, verticalParentWrapContent,
2846                     horizontalParentWrapContent, isTerminalWidget[VERTICAL], parentMin,
2847                     parentMax, mListDimensionBehaviors[DIMENSION_VERTICAL],
2848                     wrapContent, mTop, mBottom, mY, height,
2849                     mMinHeight, mMaxDimension[VERTICAL], mVerticalBiasPercent, useVerticalRatio,
2850                     mListDimensionBehaviors[HORIZONTAL] == MATCH_CONSTRAINT,
2851                     inVerticalChain, inHorizontalChain, isInVerticalBarrier,
2852                     matchConstraintDefaultHeight, matchConstraintDefaultWidth,
2853                     mMatchConstraintMinHeight, mMatchConstraintMaxHeight,
2854                     mMatchConstraintPercentHeight, applyPosition);
2855         }
2856 
2857         if (useRatio) {
2858             int strength = SolverVariable.STRENGTH_FIXED;
2859             if (mResolvedDimensionRatioSide == VERTICAL) {
2860                 system.addRatio(bottom, top, right, left, mResolvedDimensionRatio, strength);
2861             } else {
2862                 system.addRatio(right, left, bottom, top, mResolvedDimensionRatio, strength);
2863             }
2864         }
2865 
2866         if (mCenter.isConnected()) {
2867             system.addCenterPoint(this, mCenter.getTarget().getOwner(),
2868                     (float) Math.toRadians(mCircleConstraintAngle + 90), mCenter.getMargin());
2869         }
2870 
2871         if (LinearSystem.FULL_DEBUG) {
2872             System.out.println("\n----------------------------------------------");
2873             System.out.println("-- DONE adding " + getDebugName() + " to the solver");
2874             System.out.println("----------------------------------------------\n");
2875         }
2876         mResolvedHorizontal = false;
2877         mResolvedVertical = false;
2878         if (LinearSystem.sMetrics != null) {
2879             LinearSystem.sMetrics.mEquations = system.getNumEquations();
2880             LinearSystem.sMetrics.mVariables = system.getNumVariables();
2881         }
2882 
2883     }
2884 
2885     /**
2886      * Used to select which widgets should be added to the solver first
2887      */
addFirst()2888     boolean addFirst() {
2889         return this instanceof VirtualLayout || this instanceof Guideline;
2890     }
2891 
2892     /**
2893      * Resolves the dimension ratio parameters
2894      * (mResolvedDimensionRatioSide & mDimensionRatio)
2895      *
2896      * @param hParentWrapContent       true if parent is in wrap content horizontally
2897      * @param vParentWrapContent       true if parent is in wrap content vertically
2898      * @param horizontalDimensionFixed true if this widget horizontal dimension is fixed
2899      * @param verticalDimensionFixed   true if this widget vertical dimension is fixed
2900      */
setupDimensionRatio(boolean hParentWrapContent, boolean vParentWrapContent, boolean horizontalDimensionFixed, boolean verticalDimensionFixed)2901     public void setupDimensionRatio(boolean hParentWrapContent,
2902             boolean vParentWrapContent,
2903             boolean horizontalDimensionFixed,
2904             boolean verticalDimensionFixed) {
2905         if (mResolvedDimensionRatioSide == UNKNOWN) {
2906             if (horizontalDimensionFixed && !verticalDimensionFixed) {
2907                 mResolvedDimensionRatioSide = HORIZONTAL;
2908             } else if (!horizontalDimensionFixed && verticalDimensionFixed) {
2909                 mResolvedDimensionRatioSide = VERTICAL;
2910                 if (mDimensionRatioSide == UNKNOWN) {
2911                     // need to reverse the ratio as the parsing is done in horizontal mode
2912                     mResolvedDimensionRatio = 1 / mResolvedDimensionRatio;
2913                 }
2914             }
2915         }
2916 
2917         if (mResolvedDimensionRatioSide == HORIZONTAL
2918                 && !(mTop.isConnected() && mBottom.isConnected())) {
2919             mResolvedDimensionRatioSide = VERTICAL;
2920         } else if (mResolvedDimensionRatioSide == VERTICAL
2921                 && !(mLeft.isConnected() && mRight.isConnected())) {
2922             mResolvedDimensionRatioSide = HORIZONTAL;
2923         }
2924 
2925         // if dimension is still unknown... check parentWrap
2926         if (mResolvedDimensionRatioSide == UNKNOWN) {
2927             if (!(mTop.isConnected() && mBottom.isConnected()
2928                     && mLeft.isConnected() && mRight.isConnected())) {
2929                 // only do that if not all connections are set
2930                 if (mTop.isConnected() && mBottom.isConnected()) {
2931                     mResolvedDimensionRatioSide = HORIZONTAL;
2932                 } else if (mLeft.isConnected() && mRight.isConnected()) {
2933                     mResolvedDimensionRatio = 1 / mResolvedDimensionRatio;
2934                     mResolvedDimensionRatioSide = VERTICAL;
2935                 }
2936             }
2937         }
2938 
2939         if (DO_NOT_USE && mResolvedDimensionRatioSide == UNKNOWN) {
2940             if (hParentWrapContent && !vParentWrapContent) {
2941                 mResolvedDimensionRatioSide = HORIZONTAL;
2942             } else if (!hParentWrapContent && vParentWrapContent) {
2943                 mResolvedDimensionRatio = 1 / mResolvedDimensionRatio;
2944                 mResolvedDimensionRatioSide = VERTICAL;
2945             }
2946         }
2947 
2948         if (mResolvedDimensionRatioSide == UNKNOWN) {
2949             if (mMatchConstraintMinWidth > 0 && mMatchConstraintMinHeight == 0) {
2950                 mResolvedDimensionRatioSide = HORIZONTAL;
2951             } else if (mMatchConstraintMinWidth == 0 && mMatchConstraintMinHeight > 0) {
2952                 mResolvedDimensionRatio = 1 / mResolvedDimensionRatio;
2953                 mResolvedDimensionRatioSide = VERTICAL;
2954             }
2955         }
2956 
2957         if (DO_NOT_USE && mResolvedDimensionRatioSide == UNKNOWN
2958                 && hParentWrapContent && vParentWrapContent) {
2959             mResolvedDimensionRatio = 1 / mResolvedDimensionRatio;
2960             mResolvedDimensionRatioSide = VERTICAL;
2961         }
2962     }
2963 
2964     /**
2965      * Apply the constraints in the system depending on the existing anchors, in one dimension
2966      *
2967      * @param system                the linear system we are adding constraints to
2968      * @param wrapContent           is the widget trying to wrap its content
2969      *                              (i.e. its size will depends on its content)
2970      * @param beginAnchor           the first anchor
2971      * @param endAnchor             the second anchor
2972      * @param beginPosition         the original position of the anchor
2973      * @param dimension             the dimension
2974      * @param matchPercentDimension the percentage relative to the parent,
2975      *                              applied if in match constraint and percent mode
2976      */
applyConstraints(LinearSystem system, boolean isHorizontal, boolean parentWrapContent, boolean oppositeParentWrapContent, boolean isTerminal, SolverVariable parentMin, SolverVariable parentMax, DimensionBehaviour dimensionBehaviour, boolean wrapContent, ConstraintAnchor beginAnchor, ConstraintAnchor endAnchor, int beginPosition, int dimension, int minDimension, int maxDimension, float bias, boolean useRatio, boolean oppositeVariable, boolean inChain, boolean oppositeInChain, boolean inBarrier, int matchConstraintDefault, int oppositeMatchConstraintDefault, int matchMinDimension, int matchMaxDimension, float matchPercentDimension, boolean applyPosition)2977     private void applyConstraints(LinearSystem system, boolean isHorizontal,
2978             boolean parentWrapContent, boolean oppositeParentWrapContent,
2979             boolean isTerminal, SolverVariable parentMin,
2980             SolverVariable parentMax,
2981             DimensionBehaviour dimensionBehaviour, boolean wrapContent,
2982             ConstraintAnchor beginAnchor, ConstraintAnchor endAnchor,
2983             int beginPosition, int dimension, int minDimension,
2984             int maxDimension, float bias, boolean useRatio,
2985             boolean oppositeVariable, boolean inChain,
2986             boolean oppositeInChain, boolean inBarrier,
2987             int matchConstraintDefault,
2988             int oppositeMatchConstraintDefault,
2989             int matchMinDimension, int matchMaxDimension,
2990             float matchPercentDimension, boolean applyPosition) {
2991         SolverVariable begin = system.createObjectVariable(beginAnchor);
2992         SolverVariable end = system.createObjectVariable(endAnchor);
2993         SolverVariable beginTarget = system.createObjectVariable(beginAnchor.getTarget());
2994         SolverVariable endTarget = system.createObjectVariable(endAnchor.getTarget());
2995 
2996         if (system.getMetrics() != null) {
2997             system.getMetrics().nonresolvedWidgets++;
2998         }
2999 
3000         boolean isBeginConnected = beginAnchor.isConnected();
3001         boolean isEndConnected = endAnchor.isConnected();
3002         boolean isCenterConnected = mCenter.isConnected();
3003 
3004         boolean variableSize = false;
3005 
3006         int numConnections = 0;
3007         if (isBeginConnected) {
3008             numConnections++;
3009         }
3010         if (isEndConnected) {
3011             numConnections++;
3012         }
3013         if (isCenterConnected) {
3014             numConnections++;
3015         }
3016 
3017         if (useRatio) {
3018             matchConstraintDefault = MATCH_CONSTRAINT_RATIO;
3019         }
3020         switch (dimensionBehaviour) {
3021             case FIXED: {
3022                 variableSize = false;
3023             }
3024             break;
3025             case WRAP_CONTENT: {
3026                 variableSize = false;
3027             }
3028             break;
3029             case MATCH_PARENT: {
3030                 variableSize = false;
3031             }
3032             break;
3033             case MATCH_CONSTRAINT: {
3034                 variableSize = matchConstraintDefault != MATCH_CONSTRAINT_RATIO_RESOLVED;
3035             }
3036             break;
3037         }
3038 
3039 
3040         if (mWidthOverride != -1 && isHorizontal) {
3041             if (FULL_DEBUG) {
3042                 System.out.println("OVERRIDE WIDTH to " + mWidthOverride);
3043             }
3044             variableSize = false;
3045             dimension = mWidthOverride;
3046             mWidthOverride = -1;
3047         }
3048         if (mHeightOverride != -1 && !isHorizontal) {
3049             if (FULL_DEBUG) {
3050                 System.out.println("OVERRIDE HEIGHT to " + mHeightOverride);
3051             }
3052             variableSize = false;
3053             dimension = mHeightOverride;
3054             mHeightOverride = -1;
3055         }
3056 
3057         if (mVisibility == ConstraintWidget.GONE) {
3058             dimension = 0;
3059             variableSize = false;
3060         }
3061 
3062         // First apply starting direct connections (more solver-friendly)
3063         if (applyPosition) {
3064             if (!isBeginConnected && !isEndConnected && !isCenterConnected) {
3065                 system.addEquality(begin, beginPosition);
3066             } else if (isBeginConnected && !isEndConnected) {
3067                 system.addEquality(begin, beginTarget,
3068                         beginAnchor.getMargin(), SolverVariable.STRENGTH_FIXED);
3069             }
3070         }
3071 
3072         // Then apply the dimension
3073         if (!variableSize) {
3074             if (wrapContent) {
3075                 system.addEquality(end, begin, 0, SolverVariable.STRENGTH_HIGH);
3076                 if (minDimension > 0) {
3077                     system.addGreaterThan(end, begin, minDimension, SolverVariable.STRENGTH_FIXED);
3078                 }
3079                 if (maxDimension < Integer.MAX_VALUE) {
3080                     system.addLowerThan(end, begin, maxDimension, SolverVariable.STRENGTH_FIXED);
3081                 }
3082             } else {
3083                 system.addEquality(end, begin, dimension, SolverVariable.STRENGTH_FIXED);
3084             }
3085         } else {
3086             if (numConnections != 2
3087                     && !useRatio
3088                     && ((matchConstraintDefault == MATCH_CONSTRAINT_WRAP)
3089                     || (matchConstraintDefault == MATCH_CONSTRAINT_SPREAD))) {
3090                 variableSize = false;
3091                 int d = Math.max(matchMinDimension, dimension);
3092                 if (matchMaxDimension > 0) {
3093                     d = Math.min(matchMaxDimension, d);
3094                 }
3095                 system.addEquality(end, begin, d, SolverVariable.STRENGTH_FIXED);
3096             } else {
3097                 if (matchMinDimension == WRAP) {
3098                     matchMinDimension = dimension;
3099                 }
3100                 if (matchMaxDimension == WRAP) {
3101                     matchMaxDimension = dimension;
3102                 }
3103                 if (dimension > 0
3104                         && matchConstraintDefault != MATCH_CONSTRAINT_WRAP) {
3105                     if (USE_WRAP_DIMENSION_FOR_SPREAD
3106                             && (matchConstraintDefault == MATCH_CONSTRAINT_SPREAD)) {
3107                         system.addGreaterThan(end, begin, dimension,
3108                                 SolverVariable.STRENGTH_HIGHEST);
3109                     }
3110                     dimension = 0;
3111                 }
3112 
3113                 if (matchMinDimension > 0) {
3114                     system.addGreaterThan(end, begin, matchMinDimension,
3115                             SolverVariable.STRENGTH_FIXED);
3116                     dimension = Math.max(dimension, matchMinDimension);
3117                 }
3118                 if (matchMaxDimension > 0) {
3119                     boolean applyLimit = true;
3120                     if (parentWrapContent && matchConstraintDefault == MATCH_CONSTRAINT_WRAP) {
3121                         applyLimit = false;
3122                     }
3123                     if (applyLimit) {
3124                         system.addLowerThan(end, begin,
3125                                 matchMaxDimension, SolverVariable.STRENGTH_FIXED);
3126                     }
3127                     dimension = Math.min(dimension, matchMaxDimension);
3128                 }
3129                 if (matchConstraintDefault == MATCH_CONSTRAINT_WRAP) {
3130                     if (parentWrapContent) {
3131                         system.addEquality(end, begin, dimension, SolverVariable.STRENGTH_FIXED);
3132                     } else if (inChain) {
3133                         system.addEquality(end, begin, dimension, SolverVariable.STRENGTH_EQUALITY);
3134                         system.addLowerThan(end, begin, dimension, SolverVariable.STRENGTH_FIXED);
3135                     } else {
3136                         system.addEquality(end, begin, dimension, SolverVariable.STRENGTH_EQUALITY);
3137                         system.addLowerThan(end, begin, dimension, SolverVariable.STRENGTH_FIXED);
3138                     }
3139                 } else if (matchConstraintDefault == MATCH_CONSTRAINT_PERCENT) {
3140                     SolverVariable percentBegin = null;
3141                     SolverVariable percentEnd = null;
3142                     if (beginAnchor.getType() == ConstraintAnchor.Type.TOP
3143                             || beginAnchor.getType() == ConstraintAnchor.Type.BOTTOM) {
3144                         // vertical
3145                         percentBegin = system.createObjectVariable(
3146                                 mParent.getAnchor(ConstraintAnchor.Type.TOP));
3147                         percentEnd = system.createObjectVariable(
3148                                 mParent.getAnchor(ConstraintAnchor.Type.BOTTOM));
3149                     } else {
3150                         percentBegin = system.createObjectVariable(
3151                                 mParent.getAnchor(ConstraintAnchor.Type.LEFT));
3152                         percentEnd = system.createObjectVariable(
3153                                 mParent.getAnchor(ConstraintAnchor.Type.RIGHT));
3154                     }
3155                     system.addConstraint(system.createRow().createRowDimensionRatio(end,
3156                             begin, percentEnd, percentBegin, matchPercentDimension));
3157                     if (parentWrapContent) {
3158                         variableSize = false;
3159                     }
3160                 } else {
3161                     isTerminal = true;
3162                 }
3163             }
3164         }
3165 
3166         if (!applyPosition || inChain) {
3167             // If we don't need to apply the position, let's finish now.
3168             if (LinearSystem.FULL_DEBUG) {
3169                 System.out.println("only deal with dimension for " + mDebugName
3170                         + ", not positioning (applyPosition: "
3171                         + applyPosition + " inChain: " + inChain + ")");
3172             }
3173             if (numConnections < 2 && parentWrapContent && isTerminal) {
3174                 system.addGreaterThan(begin, parentMin, 0, SolverVariable.STRENGTH_FIXED);
3175                 boolean applyEnd = isHorizontal || (mBaseline.mTarget == null);
3176                 if (!isHorizontal && mBaseline.mTarget != null) {
3177                     // generally we wouldn't take the current widget in the wrap content,
3178                     // but if the connected element is a ratio widget,
3179                     // then we can contribute (as the ratio widget may not be enough by itself)
3180                     // to it.
3181                     ConstraintWidget target = mBaseline.mTarget.mOwner;
3182                     if (target.mDimensionRatio != 0
3183                             && target.mListDimensionBehaviors[0] == MATCH_CONSTRAINT
3184                             && target.mListDimensionBehaviors[1] == MATCH_CONSTRAINT) {
3185                         applyEnd = true;
3186                     } else {
3187                         applyEnd = false;
3188                     }
3189                 }
3190                 if (applyEnd) {
3191                     if (FULL_DEBUG) {
3192                         System.out.println("<>4 ADDING WRAP GREATER FOR " + getDebugName());
3193                     }
3194                     system.addGreaterThan(parentMax, end, 0, SolverVariable.STRENGTH_FIXED);
3195                 }
3196             }
3197             return;
3198         }
3199 
3200         // Ok, we are dealing with single or centered constraints, let's apply them
3201 
3202         int wrapStrength = SolverVariable.STRENGTH_EQUALITY;
3203 
3204         if (!isBeginConnected && !isEndConnected && !isCenterConnected) {
3205             // note we already applied the start position before, no need to redo it...
3206         } else if (isBeginConnected && !isEndConnected) {
3207             // note we already applied the start position before, no need to redo it...
3208 
3209             // If we are constrained to a barrier, make sure that we are not bypassed in the wrap
3210             ConstraintWidget beginWidget = beginAnchor.mTarget.mOwner;
3211             if (parentWrapContent && beginWidget instanceof Barrier) {
3212                 wrapStrength = SolverVariable.STRENGTH_FIXED;
3213             }
3214         } else if (!isBeginConnected && isEndConnected) {
3215             system.addEquality(end, endTarget,
3216                     -endAnchor.getMargin(), SolverVariable.STRENGTH_FIXED);
3217             if (parentWrapContent) {
3218                 if (mOptimizeWrapO && begin.isFinalValue && mParent != null) {
3219                     ConstraintWidgetContainer container = (ConstraintWidgetContainer) mParent;
3220                     if (isHorizontal) {
3221                         container.addHorizontalWrapMinVariable(beginAnchor);
3222                     } else {
3223                         container.addVerticalWrapMinVariable(beginAnchor);
3224                     }
3225                 } else {
3226                     if (FULL_DEBUG) {
3227                         System.out.println("<>5 ADDING WRAP GREATER FOR " + getDebugName());
3228                     }
3229                     system.addGreaterThan(begin, parentMin, 0, SolverVariable.STRENGTH_EQUALITY);
3230                 }
3231             }
3232         } else if (isBeginConnected && isEndConnected) {
3233             boolean applyBoundsCheck = true;
3234             boolean applyCentering = false;
3235             boolean applyStrongChecks = false;
3236             boolean applyRangeCheck = false;
3237             int rangeCheckStrength = SolverVariable.STRENGTH_EQUALITY;
3238 
3239             // TODO: might not need it here (it's overridden)
3240             int boundsCheckStrength = SolverVariable.STRENGTH_HIGHEST;
3241             int centeringStrength = SolverVariable.STRENGTH_BARRIER;
3242 
3243             if (parentWrapContent) {
3244                 rangeCheckStrength = SolverVariable.STRENGTH_EQUALITY;
3245             }
3246             ConstraintWidget beginWidget = beginAnchor.mTarget.mOwner;
3247             ConstraintWidget endWidget = endAnchor.mTarget.mOwner;
3248             ConstraintWidget parent = getParent();
3249 
3250             if (variableSize) {
3251                 if (matchConstraintDefault == MATCH_CONSTRAINT_SPREAD) {
3252                     if (matchMaxDimension == 0 && matchMinDimension == 0) {
3253                         applyStrongChecks = true;
3254                         rangeCheckStrength = SolverVariable.STRENGTH_FIXED;
3255                         boundsCheckStrength = SolverVariable.STRENGTH_FIXED;
3256                         // Optimization in case of centering in parent
3257                         if (beginTarget.isFinalValue && endTarget.isFinalValue) {
3258                             system.addEquality(begin, beginTarget,
3259                                     beginAnchor.getMargin(), SolverVariable.STRENGTH_FIXED);
3260                             system.addEquality(end, endTarget,
3261                                     -endAnchor.getMargin(), SolverVariable.STRENGTH_FIXED);
3262                             return;
3263                         }
3264                     } else {
3265                         applyCentering = true;
3266                         rangeCheckStrength = SolverVariable.STRENGTH_EQUALITY;
3267                         boundsCheckStrength = SolverVariable.STRENGTH_EQUALITY;
3268                         applyBoundsCheck = true;
3269                         applyRangeCheck = true;
3270                     }
3271                     if (beginWidget instanceof Barrier || endWidget instanceof Barrier) {
3272                         boundsCheckStrength = SolverVariable.STRENGTH_HIGHEST;
3273                     }
3274                 } else if (matchConstraintDefault == MATCH_CONSTRAINT_PERCENT) {
3275                     applyCentering = true;
3276                     rangeCheckStrength = SolverVariable.STRENGTH_EQUALITY;
3277                     boundsCheckStrength = SolverVariable.STRENGTH_EQUALITY;
3278                     applyBoundsCheck = true;
3279                     applyRangeCheck = true;
3280                     if (beginWidget instanceof Barrier || endWidget instanceof Barrier) {
3281                         boundsCheckStrength = SolverVariable.STRENGTH_HIGHEST;
3282                     }
3283                 } else if (matchConstraintDefault == MATCH_CONSTRAINT_WRAP) {
3284                     applyCentering = true;
3285                     applyRangeCheck = true;
3286                     rangeCheckStrength = SolverVariable.STRENGTH_FIXED;
3287                 } else if (matchConstraintDefault == MATCH_CONSTRAINT_RATIO) {
3288                     if (mResolvedDimensionRatioSide == UNKNOWN) {
3289                         applyCentering = true;
3290                         applyRangeCheck = true;
3291                         applyStrongChecks = true;
3292                         rangeCheckStrength = SolverVariable.STRENGTH_FIXED;
3293                         boundsCheckStrength = SolverVariable.STRENGTH_EQUALITY;
3294                         if (oppositeInChain) {
3295                             boundsCheckStrength = SolverVariable.STRENGTH_EQUALITY;
3296                             centeringStrength = SolverVariable.STRENGTH_HIGHEST;
3297                             if (parentWrapContent) {
3298                                 centeringStrength = SolverVariable.STRENGTH_EQUALITY;
3299                             }
3300                         } else {
3301                             centeringStrength = SolverVariable.STRENGTH_FIXED;
3302                         }
3303                     } else {
3304                         applyCentering = true;
3305                         applyRangeCheck = true;
3306                         applyStrongChecks = true;
3307                         if (useRatio) {
3308                             // useRatio is true
3309                             // if the side we base ourselves on for the ratio is this one
3310                             // if that's not the case, we need to have a stronger constraint.
3311                             boolean otherSideInvariable =
3312                                     oppositeMatchConstraintDefault == MATCH_CONSTRAINT_PERCENT
3313                                             || oppositeMatchConstraintDefault
3314                                             == MATCH_CONSTRAINT_WRAP;
3315                             if (!otherSideInvariable) {
3316                                 rangeCheckStrength = SolverVariable.STRENGTH_FIXED;
3317                                 boundsCheckStrength = SolverVariable.STRENGTH_EQUALITY;
3318                             }
3319                         } else {
3320                             rangeCheckStrength = SolverVariable.STRENGTH_EQUALITY;
3321                             if (matchMaxDimension > 0) {
3322                                 boundsCheckStrength = SolverVariable.STRENGTH_EQUALITY;
3323                             } else if (matchMaxDimension == 0 && matchMinDimension == 0) {
3324                                 if (!oppositeInChain) {
3325                                     boundsCheckStrength = SolverVariable.STRENGTH_FIXED;
3326                                 } else {
3327                                     if (beginWidget != parent && endWidget != parent) {
3328                                         rangeCheckStrength = SolverVariable.STRENGTH_HIGHEST;
3329                                     } else {
3330                                         rangeCheckStrength = SolverVariable.STRENGTH_EQUALITY;
3331                                     }
3332                                     boundsCheckStrength = SolverVariable.STRENGTH_HIGHEST;
3333                                 }
3334                             }
3335                         }
3336                     }
3337                 }
3338             } else {
3339                 applyCentering = true;
3340                 applyRangeCheck = true;
3341 
3342                 // Let's optimize away if we can...
3343                 if (beginTarget.isFinalValue && endTarget.isFinalValue) {
3344                     system.addCentering(begin, beginTarget, beginAnchor.getMargin(),
3345                             bias, endTarget, end, endAnchor.getMargin(),
3346                             SolverVariable.STRENGTH_FIXED);
3347                     if (parentWrapContent && isTerminal) {
3348                         int margin = 0;
3349                         if (endAnchor.mTarget != null) {
3350                             margin = endAnchor.getMargin();
3351                         }
3352                         if (endTarget != parentMax) { // if not already applied
3353                             if (FULL_DEBUG) {
3354                                 System.out.println("<>6 ADDING WRAP GREATER FOR " + getDebugName());
3355                             }
3356                             system.addGreaterThan(parentMax, end, margin, wrapStrength);
3357                         }
3358                     }
3359                     return;
3360                 }
3361             }
3362 
3363             if (applyRangeCheck && beginTarget == endTarget && beginWidget != parent) {
3364                 // no need to apply range / bounds check if we are centered on the same anchor
3365                 applyRangeCheck = false;
3366                 applyBoundsCheck = false;
3367             }
3368 
3369             if (applyCentering) {
3370                 if (!variableSize && !oppositeVariable && !oppositeInChain
3371                         && beginTarget == parentMin && endTarget == parentMax) {
3372                     // for fixed size widgets, we can simplify the constraints
3373                     centeringStrength = SolverVariable.STRENGTH_FIXED;
3374                     rangeCheckStrength = SolverVariable.STRENGTH_FIXED;
3375                     applyBoundsCheck = false;
3376                     parentWrapContent = false;
3377                 }
3378 
3379                 system.addCentering(begin, beginTarget, beginAnchor.getMargin(),
3380                         bias, endTarget, end, endAnchor.getMargin(), centeringStrength);
3381             }
3382 
3383             if (mVisibility == GONE && !endAnchor.hasDependents()) {
3384                 return;
3385             }
3386 
3387             if (applyRangeCheck) {
3388                 if (parentWrapContent && beginTarget != endTarget
3389                         && !variableSize) {
3390                     if (beginWidget instanceof Barrier || endWidget instanceof Barrier) {
3391                         rangeCheckStrength = SolverVariable.STRENGTH_BARRIER;
3392                     }
3393                 }
3394                 system.addGreaterThan(begin, beginTarget,
3395                         beginAnchor.getMargin(), rangeCheckStrength);
3396                 system.addLowerThan(end, endTarget, -endAnchor.getMargin(), rangeCheckStrength);
3397             }
3398 
3399             if (parentWrapContent && inBarrier // if we are referenced by a barrier
3400                     && !(beginWidget instanceof Barrier || endWidget instanceof Barrier)
3401                     && !(endWidget == parent)) {
3402                 // ... but not directly constrained by it
3403                 // ... then make sure we can hold our own
3404                 boundsCheckStrength = SolverVariable.STRENGTH_BARRIER;
3405                 rangeCheckStrength = SolverVariable.STRENGTH_BARRIER;
3406                 applyBoundsCheck = true;
3407             }
3408 
3409             if (applyBoundsCheck) {
3410                 if (applyStrongChecks && (!oppositeInChain || oppositeParentWrapContent)) {
3411                     int strength = boundsCheckStrength;
3412                     if (beginWidget == parent || endWidget == parent) {
3413                         strength = SolverVariable.STRENGTH_BARRIER;
3414                     }
3415                     if (beginWidget instanceof Guideline || endWidget instanceof Guideline) {
3416                         strength = SolverVariable.STRENGTH_EQUALITY;
3417                     }
3418                     if (beginWidget instanceof Barrier || endWidget instanceof Barrier) {
3419                         strength = SolverVariable.STRENGTH_EQUALITY;
3420                     }
3421                     if (oppositeInChain) {
3422                         strength = SolverVariable.STRENGTH_EQUALITY;
3423                     }
3424                     boundsCheckStrength = Math.max(strength, boundsCheckStrength);
3425                 }
3426 
3427                 if (parentWrapContent) {
3428                     boundsCheckStrength = Math.min(rangeCheckStrength, boundsCheckStrength);
3429                     if (useRatio && !oppositeInChain
3430                             && (beginWidget == parent || endWidget == parent)) {
3431                         // When using ratio, relax some strength to allow other parts of the system
3432                         // to take precedence rather than driving it
3433                         boundsCheckStrength = SolverVariable.STRENGTH_HIGHEST;
3434                     }
3435                 }
3436                 system.addEquality(begin, beginTarget,
3437                         beginAnchor.getMargin(), boundsCheckStrength);
3438                 system.addEquality(end, endTarget, -endAnchor.getMargin(), boundsCheckStrength);
3439             }
3440 
3441             if (parentWrapContent) {
3442                 int margin = 0;
3443                 if (parentMin == beginTarget) {
3444                     margin = beginAnchor.getMargin();
3445                 }
3446                 if (beginTarget != parentMin) { // already done otherwise
3447                     if (FULL_DEBUG) {
3448                         System.out.println("<>7 ADDING WRAP GREATER FOR " + getDebugName());
3449                     }
3450                     system.addGreaterThan(begin, parentMin, margin, wrapStrength);
3451                 }
3452             }
3453 
3454             if (parentWrapContent && variableSize && minDimension == 0 && matchMinDimension == 0) {
3455                 if (FULL_DEBUG) {
3456                     System.out.println("<>8 ADDING WRAP GREATER FOR " + getDebugName());
3457                 }
3458                 if (variableSize && matchConstraintDefault == MATCH_CONSTRAINT_RATIO) {
3459                     system.addGreaterThan(end, begin, 0, SolverVariable.STRENGTH_FIXED);
3460                 } else {
3461                     system.addGreaterThan(end, begin, 0, wrapStrength);
3462                 }
3463             }
3464         }
3465 
3466         if (parentWrapContent && isTerminal) {
3467             int margin = 0;
3468             if (endAnchor.mTarget != null) {
3469                 margin = endAnchor.getMargin();
3470             }
3471             if (endTarget != parentMax) { // if not already applied
3472                 if (mOptimizeWrapO && end.isFinalValue && mParent != null) {
3473                     ConstraintWidgetContainer container = (ConstraintWidgetContainer) mParent;
3474                     if (isHorizontal) {
3475                         container.addHorizontalWrapMaxVariable(endAnchor);
3476                     } else {
3477                         container.addVerticalWrapMaxVariable(endAnchor);
3478                     }
3479                     return;
3480                 }
3481                 if (FULL_DEBUG) {
3482                     System.out.println("<>9 ADDING WRAP GREATER FOR " + getDebugName());
3483                 }
3484                 system.addGreaterThan(parentMax, end, margin, wrapStrength);
3485             }
3486         }
3487     }
3488 
3489     /**
3490      * Update the widget from the values generated by the solver
3491      *
3492      * @param system   the solver we get the values from.
3493      * @param optimize true if {@link Optimizer#OPTIMIZATION_GRAPH} is on
3494      */
updateFromSolver(LinearSystem system, boolean optimize)3495     public void updateFromSolver(LinearSystem system, boolean optimize) {
3496         int left = system.getObjectVariableValue(mLeft);
3497         int top = system.getObjectVariableValue(mTop);
3498         int right = system.getObjectVariableValue(mRight);
3499         int bottom = system.getObjectVariableValue(mBottom);
3500 
3501         if (optimize && mHorizontalRun != null
3502                 && mHorizontalRun.start.resolved && mHorizontalRun.end.resolved) {
3503             left = mHorizontalRun.start.value;
3504             right = mHorizontalRun.end.value;
3505         }
3506         if (optimize && mVerticalRun != null
3507                 && mVerticalRun.start.resolved && mVerticalRun.end.resolved) {
3508             top = mVerticalRun.start.value;
3509             bottom = mVerticalRun.end.value;
3510         }
3511 
3512         int w = right - left;
3513         int h = bottom - top;
3514         if (w < 0 || h < 0
3515                 || left == Integer.MIN_VALUE || left == Integer.MAX_VALUE
3516                 || top == Integer.MIN_VALUE || top == Integer.MAX_VALUE
3517                 || right == Integer.MIN_VALUE || right == Integer.MAX_VALUE
3518                 || bottom == Integer.MIN_VALUE || bottom == Integer.MAX_VALUE) {
3519             left = 0;
3520             top = 0;
3521             right = 0;
3522             bottom = 0;
3523         }
3524         setFrame(left, top, right, bottom);
3525         if (DEBUG) {
3526             System.out.println(" *** UPDATE FROM SOLVER " + this);
3527         }
3528     }
3529 
3530     // @TODO: add description
copy(ConstraintWidget src, HashMap<ConstraintWidget, ConstraintWidget> map)3531     public void copy(ConstraintWidget src, HashMap<ConstraintWidget, ConstraintWidget> map) {
3532         // Support for direct resolution
3533         mHorizontalResolution = src.mHorizontalResolution;
3534         mVerticalResolution = src.mVerticalResolution;
3535 
3536         mMatchConstraintDefaultWidth = src.mMatchConstraintDefaultWidth;
3537         mMatchConstraintDefaultHeight = src.mMatchConstraintDefaultHeight;
3538 
3539         mResolvedMatchConstraintDefault[0] = src.mResolvedMatchConstraintDefault[0];
3540         mResolvedMatchConstraintDefault[1] = src.mResolvedMatchConstraintDefault[1];
3541 
3542         mMatchConstraintMinWidth = src.mMatchConstraintMinWidth;
3543         mMatchConstraintMaxWidth = src.mMatchConstraintMaxWidth;
3544         mMatchConstraintMinHeight = src.mMatchConstraintMinHeight;
3545         mMatchConstraintMaxHeight = src.mMatchConstraintMaxHeight;
3546         mMatchConstraintPercentHeight = src.mMatchConstraintPercentHeight;
3547         mIsWidthWrapContent = src.mIsWidthWrapContent;
3548         mIsHeightWrapContent = src.mIsHeightWrapContent;
3549 
3550         mResolvedDimensionRatioSide = src.mResolvedDimensionRatioSide;
3551         mResolvedDimensionRatio = src.mResolvedDimensionRatio;
3552 
3553         mMaxDimension = Arrays.copyOf(src.mMaxDimension, src.mMaxDimension.length);
3554         mCircleConstraintAngle = src.mCircleConstraintAngle;
3555 
3556         mHasBaseline = src.mHasBaseline;
3557         mInPlaceholder = src.mInPlaceholder;
3558 
3559         // The anchors available on the widget
3560         // note: all anchors should be added to the mAnchors array (see addAnchors())
3561 
3562         mLeft.reset();
3563         mTop.reset();
3564         mRight.reset();
3565         mBottom.reset();
3566         mBaseline.reset();
3567         mCenterX.reset();
3568         mCenterY.reset();
3569         mCenter.reset();
3570         mListDimensionBehaviors = Arrays.copyOf(mListDimensionBehaviors, 2);
3571         mParent = (mParent == null) ? null : map.get(src.mParent);
3572 
3573         mWidth = src.mWidth;
3574         mHeight = src.mHeight;
3575         mDimensionRatio = src.mDimensionRatio;
3576         mDimensionRatioSide = src.mDimensionRatioSide;
3577 
3578         mX = src.mX;
3579         mY = src.mY;
3580         mRelX = src.mRelX;
3581         mRelY = src.mRelY;
3582 
3583         mOffsetX = src.mOffsetX;
3584         mOffsetY = src.mOffsetY;
3585 
3586         mBaselineDistance = src.mBaselineDistance;
3587         mMinWidth = src.mMinWidth;
3588         mMinHeight = src.mMinHeight;
3589 
3590         mHorizontalBiasPercent = src.mHorizontalBiasPercent;
3591         mVerticalBiasPercent = src.mVerticalBiasPercent;
3592 
3593         mCompanionWidget = src.mCompanionWidget;
3594         mContainerItemSkip = src.mContainerItemSkip;
3595         mVisibility = src.mVisibility;
3596         mAnimated = src.mAnimated;
3597         mDebugName = src.mDebugName;
3598         mType = src.mType;
3599 
3600         mDistToTop = src.mDistToTop;
3601         mDistToLeft = src.mDistToLeft;
3602         mDistToRight = src.mDistToRight;
3603         mDistToBottom = src.mDistToBottom;
3604         mLeftHasCentered = src.mLeftHasCentered;
3605         mRightHasCentered = src.mRightHasCentered;
3606 
3607         mTopHasCentered = src.mTopHasCentered;
3608         mBottomHasCentered = src.mBottomHasCentered;
3609 
3610         mHorizontalWrapVisited = src.mHorizontalWrapVisited;
3611         mVerticalWrapVisited = src.mVerticalWrapVisited;
3612 
3613         mHorizontalChainStyle = src.mHorizontalChainStyle;
3614         mVerticalChainStyle = src.mVerticalChainStyle;
3615         mHorizontalChainFixedPosition = src.mHorizontalChainFixedPosition;
3616         mVerticalChainFixedPosition = src.mVerticalChainFixedPosition;
3617         mWeight[0] = src.mWeight[0];
3618         mWeight[1] = src.mWeight[1];
3619 
3620         mListNextMatchConstraintsWidget[0] = src.mListNextMatchConstraintsWidget[0];
3621         mListNextMatchConstraintsWidget[1] = src.mListNextMatchConstraintsWidget[1];
3622 
3623         mNextChainWidget[0] = src.mNextChainWidget[0];
3624         mNextChainWidget[1] = src.mNextChainWidget[1];
3625 
3626         mHorizontalNextWidget = (src.mHorizontalNextWidget == null)
3627                 ? null : map.get(src.mHorizontalNextWidget);
3628         mVerticalNextWidget = (src.mVerticalNextWidget == null)
3629                 ? null : map.get(src.mVerticalNextWidget);
3630     }
3631 
3632     // @TODO: add description
updateFromRuns(boolean updateHorizontal, boolean updateVertical)3633     public void updateFromRuns(boolean updateHorizontal, boolean updateVertical) {
3634         updateHorizontal &= mHorizontalRun.isResolved();
3635         updateVertical &= mVerticalRun.isResolved();
3636         int left = mHorizontalRun.start.value;
3637         int top = mVerticalRun.start.value;
3638         int right = mHorizontalRun.end.value;
3639         int bottom = mVerticalRun.end.value;
3640         int w = right - left;
3641         int h = bottom - top;
3642         if (w < 0 || h < 0
3643                 || left == Integer.MIN_VALUE || left == Integer.MAX_VALUE
3644                 || top == Integer.MIN_VALUE || top == Integer.MAX_VALUE
3645                 || right == Integer.MIN_VALUE || right == Integer.MAX_VALUE
3646                 || bottom == Integer.MIN_VALUE || bottom == Integer.MAX_VALUE) {
3647             left = 0;
3648             top = 0;
3649             right = 0;
3650             bottom = 0;
3651         }
3652 
3653         w = right - left;
3654         h = bottom - top;
3655 
3656         if (updateHorizontal) {
3657             mX = left;
3658         }
3659         if (updateVertical) {
3660             mY = top;
3661         }
3662 
3663         if (mVisibility == ConstraintWidget.GONE) {
3664             mWidth = 0;
3665             mHeight = 0;
3666             return;
3667         }
3668 
3669         // correct dimensional instability caused by rounding errors
3670         if (updateHorizontal) {
3671             if (mListDimensionBehaviors[DIMENSION_HORIZONTAL]
3672                     == DimensionBehaviour.FIXED && w < mWidth) {
3673                 w = mWidth;
3674             }
3675             mWidth = w;
3676             if (mWidth < mMinWidth) {
3677                 mWidth = mMinWidth;
3678             }
3679         }
3680 
3681         if (updateVertical) {
3682             if (mListDimensionBehaviors[DIMENSION_VERTICAL]
3683                     == DimensionBehaviour.FIXED && h < mHeight) {
3684                 h = mHeight;
3685             }
3686             mHeight = h;
3687             if (mHeight < mMinHeight) {
3688                 mHeight = mMinHeight;
3689             }
3690         }
3691 
3692     }
3693 
3694     // @TODO: add description
addChildrenToSolverByDependency(ConstraintWidgetContainer container, LinearSystem system, HashSet<ConstraintWidget> widgets, int orientation, boolean addSelf)3695     public void addChildrenToSolverByDependency(ConstraintWidgetContainer container,
3696             LinearSystem system,
3697             HashSet<ConstraintWidget> widgets,
3698             int orientation,
3699             boolean addSelf) {
3700         if (addSelf) {
3701             if (!widgets.contains(this)) {
3702                 return;
3703             }
3704             Optimizer.checkMatchParent(container, system, this);
3705             widgets.remove(this);
3706             addToSolver(system, container.optimizeFor(Optimizer.OPTIMIZATION_GRAPH));
3707         }
3708         if (orientation == HORIZONTAL) {
3709             HashSet<ConstraintAnchor> dependents = mLeft.getDependents();
3710             if (dependents != null) {
3711                 for (ConstraintAnchor anchor : dependents) {
3712                     anchor.mOwner.addChildrenToSolverByDependency(container,
3713                             system, widgets, orientation, true);
3714                 }
3715             }
3716             dependents = mRight.getDependents();
3717             if (dependents != null) {
3718                 for (ConstraintAnchor anchor : dependents) {
3719                     anchor.mOwner.addChildrenToSolverByDependency(container,
3720                             system, widgets, orientation, true);
3721                 }
3722             }
3723         } else {
3724             HashSet<ConstraintAnchor> dependents = mTop.getDependents();
3725             if (dependents != null) {
3726                 for (ConstraintAnchor anchor : dependents) {
3727                     anchor.mOwner.addChildrenToSolverByDependency(container,
3728                             system, widgets, orientation, true);
3729                 }
3730             }
3731             dependents = mBottom.getDependents();
3732             if (dependents != null) {
3733                 for (ConstraintAnchor anchor : dependents) {
3734                     anchor.mOwner.addChildrenToSolverByDependency(container,
3735                             system, widgets, orientation, true);
3736                 }
3737             }
3738             dependents = mBaseline.getDependents();
3739             if (dependents != null) {
3740                 for (ConstraintAnchor anchor : dependents) {
3741                     anchor.mOwner.addChildrenToSolverByDependency(container,
3742                             system, widgets, orientation, true);
3743                 }
3744             }
3745         }
3746         // horizontal
3747     }
3748 
3749     // @TODO: add description
getSceneString(StringBuilder ret)3750     public void getSceneString(StringBuilder ret) {
3751 
3752         ret.append("  " + stringId + ":{\n");
3753         ret.append("    actualWidth:" + mWidth);
3754         ret.append("\n");
3755         ret.append("    actualHeight:" + mHeight);
3756         ret.append("\n");
3757         ret.append("    actualLeft:" + mX);
3758         ret.append("\n");
3759         ret.append("    actualTop:" + mY);
3760         ret.append("\n");
3761         getSceneString(ret, "left", mLeft);
3762         getSceneString(ret, "top", mTop);
3763         getSceneString(ret, "right", mRight);
3764         getSceneString(ret, "bottom", mBottom);
3765         getSceneString(ret, "baseline", mBaseline);
3766         getSceneString(ret, "centerX", mCenterX);
3767         getSceneString(ret, "centerY", mCenterY);
3768         getSceneString(ret, "    width",
3769                 mWidth,
3770                 mMinWidth,
3771                 mMaxDimension[HORIZONTAL],
3772                 mWidthOverride,
3773                 mMatchConstraintMinWidth,
3774                 mMatchConstraintDefaultWidth,
3775                 mMatchConstraintPercentWidth,
3776                 mListDimensionBehaviors[HORIZONTAL],
3777                 mWeight[DIMENSION_HORIZONTAL]
3778         );
3779 
3780         getSceneString(ret, "    height",
3781                 mHeight,
3782                 mMinHeight,
3783                 mMaxDimension[VERTICAL],
3784                 mHeightOverride,
3785                 mMatchConstraintMinHeight,
3786                 mMatchConstraintDefaultHeight,
3787                 mMatchConstraintPercentHeight,
3788                 mListDimensionBehaviors[VERTICAL],
3789                 mWeight[DIMENSION_VERTICAL]);
3790         serializeDimensionRatio(ret, "    dimensionRatio", mDimensionRatio, mDimensionRatioSide);
3791         serializeAttribute(ret, "    horizontalBias", mHorizontalBiasPercent, DEFAULT_BIAS);
3792         serializeAttribute(ret, "    verticalBias", mVerticalBiasPercent, DEFAULT_BIAS);
3793         serializeAttribute(ret, "    horizontalChainStyle", mHorizontalChainStyle, CHAIN_SPREAD);
3794         serializeAttribute(ret, "    verticalChainStyle", mVerticalChainStyle, CHAIN_SPREAD);
3795 
3796         ret.append("  }");
3797 
3798     }
3799 
getSceneString(StringBuilder ret, String type, int size, int min, int max, @SuppressWarnings("unused") int override, int matchConstraintMin, int matchConstraintDefault, float matchConstraintPercent, DimensionBehaviour behavior, @SuppressWarnings("unused") float weight)3800     private void getSceneString(StringBuilder ret, String type, int size,
3801             int min, int max,
3802             @SuppressWarnings("unused") int override,
3803             int matchConstraintMin, int matchConstraintDefault,
3804             float matchConstraintPercent,
3805             DimensionBehaviour behavior,
3806             @SuppressWarnings("unused") float weight) {
3807         ret.append(type);
3808         ret.append(" :  {\n");
3809         serializeAttribute(ret, "      behavior", behavior.toString(),
3810                 DimensionBehaviour.FIXED.toString());
3811         serializeAttribute(ret, "      size", size, 0);
3812         serializeAttribute(ret, "      min", min, 0);
3813         serializeAttribute(ret, "      max", max, Integer.MAX_VALUE);
3814         serializeAttribute(ret, "      matchMin", matchConstraintMin, 0);
3815         serializeAttribute(ret, "      matchDef", matchConstraintDefault, MATCH_CONSTRAINT_SPREAD);
3816         serializeAttribute(ret, "      matchPercent", matchConstraintPercent, 1);
3817         ret.append("    },\n");
3818     }
3819 
getSceneString(StringBuilder ret, String side, ConstraintAnchor a)3820     private void getSceneString(StringBuilder ret, String side, ConstraintAnchor a) {
3821         if (a.mTarget == null) {
3822             return;
3823         }
3824         ret.append("    ");
3825         ret.append(side);
3826         ret.append(" : [ '");
3827         ret.append(a.mTarget);
3828         ret.append("'");
3829         if (a.mGoneMargin != Integer.MIN_VALUE || a.mMargin != 0) {
3830             ret.append(",");
3831             ret.append(a.mMargin);
3832             if (a.mGoneMargin != Integer.MIN_VALUE) {
3833                 ret.append(",");
3834                 ret.append(a.mGoneMargin);
3835                 ret.append(",");
3836             }
3837         }
3838         ret.append(" ] ,\n");
3839     }
3840 }
3841