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 
17 package androidx.constraintlayout.core.widgets;
18 
19 import static androidx.constraintlayout.core.LinearSystem.FULL_DEBUG;
20 import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.FIXED;
21 import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.WRAP_CONTENT;
22 
23 import androidx.constraintlayout.core.LinearSystem;
24 import androidx.constraintlayout.core.Metrics;
25 import androidx.constraintlayout.core.SolverVariable;
26 import androidx.constraintlayout.core.widgets.analyzer.BasicMeasure;
27 import androidx.constraintlayout.core.widgets.analyzer.DependencyGraph;
28 import androidx.constraintlayout.core.widgets.analyzer.Direct;
29 import androidx.constraintlayout.core.widgets.analyzer.Grouping;
30 
31 import java.lang.ref.WeakReference;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.HashSet;
35 import java.util.List;
36 
37 /**
38  * A container of ConstraintWidget that can layout its children
39  */
40 public class ConstraintWidgetContainer extends WidgetContainer {
41 
42     private static final int MAX_ITERATIONS = 8;
43 
44     private static final boolean DEBUG = FULL_DEBUG;
45     private static final boolean DEBUG_LAYOUT = false;
46     static final boolean DEBUG_GRAPH = false;
47 
48     BasicMeasure mBasicMeasureSolver = new BasicMeasure(this);
49 
50     ////////////////////////////////////////////////////////////////////////////////////////////////
51     // Graph measures
52     ////////////////////////////////////////////////////////////////////////////////////////////////
53 
54     public DependencyGraph mDependencyGraph = new DependencyGraph(this);
55     private int mPass; // number of layout passes
56 
57     /**
58      * Invalidate the graph of constraints
59      */
invalidateGraph()60     public void invalidateGraph() {
61         mDependencyGraph.invalidateGraph();
62     }
63 
64     /**
65      * Invalidate the widgets measures
66      */
invalidateMeasures()67     public void invalidateMeasures() {
68         mDependencyGraph.invalidateMeasures();
69     }
70 
71 
72     // @TODO: add description
directMeasure(boolean optimizeWrap)73     public boolean directMeasure(boolean optimizeWrap) {
74         return mDependencyGraph.directMeasure(optimizeWrap);
75 //        int paddingLeft = getX();
76 //        int paddingTop = getY();
77 //        if (mDependencyGraph.directMeasureSetup(optimizeWrap)) {
78 //            mDependencyGraph.measureWidgets();
79 //            boolean allResolved =
80 //                      mDependencyGraph.directMeasureWithOrientation(optimizeWrap, HORIZONTAL);
81 //            allResolved &= mDependencyGraph.directMeasureWithOrientation(optimizeWrap, VERTICAL);
82 //            for (ConstraintWidget child : mChildren) {
83 //                child.setDrawX(child.getDrawX() + paddingLeft);
84 //                child.setDrawY(child.getDrawY() + paddingTop);
85 //            }
86 //            setX(paddingLeft);
87 //            setY(paddingTop);
88 //            return allResolved;
89 //        }
90 //        return false;
91     }
92 
93     // @TODO: add description
directMeasureSetup(boolean optimizeWrap)94     public boolean directMeasureSetup(boolean optimizeWrap) {
95         return mDependencyGraph.directMeasureSetup(optimizeWrap);
96     }
97 
98     // @TODO: add description
directMeasureWithOrientation(boolean optimizeWrap, int orientation)99     public boolean directMeasureWithOrientation(boolean optimizeWrap, int orientation) {
100         return mDependencyGraph.directMeasureWithOrientation(optimizeWrap, orientation);
101     }
102 
103     // @TODO: add description
defineTerminalWidgets()104     public void defineTerminalWidgets() {
105         mDependencyGraph.defineTerminalWidgets(getHorizontalDimensionBehaviour(),
106                 getVerticalDimensionBehaviour());
107     }
108 
109     ////////////////////////////////////////////////////////////////////////////////////////////////
110 
111     /**
112      * Measure the layout
113      */
measure(int optimizationLevel, int widthMode, int widthSize, int heightMode, int heightSize, int lastMeasureWidth, int lastMeasureHeight, int paddingX, int paddingY)114     public long measure(int optimizationLevel, int widthMode, int widthSize,
115             int heightMode, int heightSize, int lastMeasureWidth,
116             int lastMeasureHeight, int paddingX, int paddingY) {
117         mPaddingLeft = paddingX;
118         mPaddingTop = paddingY;
119         return mBasicMeasureSolver.solverMeasure(this, optimizationLevel, paddingX, paddingY,
120                 widthMode, widthSize, heightMode, heightSize,
121                 lastMeasureWidth, lastMeasureHeight);
122     }
123 
124     // @TODO: add description
updateHierarchy()125     public void updateHierarchy() {
126         mBasicMeasureSolver.updateHierarchy(this);
127     }
128 
129     protected BasicMeasure.Measurer mMeasurer = null;
130 
131     // @TODO: add description
setMeasurer(BasicMeasure.Measurer measurer)132     public void setMeasurer(BasicMeasure.Measurer measurer) {
133         mMeasurer = measurer;
134         mDependencyGraph.setMeasurer(measurer);
135     }
136 
getMeasurer()137     public BasicMeasure.Measurer getMeasurer() {
138         return mMeasurer;
139     }
140 
141     private boolean mIsRtl = false;
142     public Metrics mMetrics;
143 
144     // @TODO: add description
fillMetrics(Metrics metrics)145     public void fillMetrics(Metrics metrics) {
146         mMetrics = metrics;
147         mSystem.fillMetrics(metrics);
148     }
149 
150     protected LinearSystem mSystem = new LinearSystem();
151 
152     int mPaddingLeft;
153     int mPaddingTop;
154     int mPaddingRight;
155     int mPaddingBottom;
156 
157     public int mHorizontalChainsSize = 0;
158     public int mVerticalChainsSize = 0;
159 
160     ChainHead[] mVerticalChainsArray = new ChainHead[4];
161     ChainHead[] mHorizontalChainsArray = new ChainHead[4];
162 
163     public boolean mGroupsWrapOptimized = false;
164     public boolean mHorizontalWrapOptimized = false;
165     public boolean mVerticalWrapOptimized = false;
166     public int mWrapFixedWidth = 0;
167     public int mWrapFixedHeight = 0;
168 
169     private int mOptimizationLevel = Optimizer.OPTIMIZATION_STANDARD;
170     public boolean mSkipSolver = false;
171 
172     private boolean mWidthMeasuredTooSmall = false;
173     private boolean mHeightMeasuredTooSmall = false;
174 
175     /*-----------------------------------------------------------------------*/
176     // Construction
177     /*-----------------------------------------------------------------------*/
178 
179     /**
180      * Default constructor
181      */
ConstraintWidgetContainer()182     public ConstraintWidgetContainer() {
183     }
184 
185     /**
186      * Constructor
187      *
188      * @param x      x position
189      * @param y      y position
190      * @param width  width of the layout
191      * @param height height of the layout
192      */
ConstraintWidgetContainer(int x, int y, int width, int height)193     public ConstraintWidgetContainer(int x, int y, int width, int height) {
194         super(x, y, width, height);
195     }
196 
197     /**
198      * Constructor
199      *
200      * @param width  width of the layout
201      * @param height height of the layout
202      */
ConstraintWidgetContainer(int width, int height)203     public ConstraintWidgetContainer(int width, int height) {
204         super(width, height);
205     }
206 
ConstraintWidgetContainer(String debugName, int width, int height)207     public ConstraintWidgetContainer(String debugName, int width, int height) {
208         super(width, height);
209         setDebugName(debugName);
210     }
211 
212     /**
213      * Resolves the system directly when possible
214      *
215      * @param value optimization level
216      */
setOptimizationLevel(int value)217     public void setOptimizationLevel(int value) {
218         mOptimizationLevel = value;
219         mSystem.USE_DEPENDENCY_ORDERING = optimizeFor(Optimizer.OPTIMIZATION_DEPENDENCY_ORDERING);
220     }
221 
222     /**
223      * Returns the current optimization level
224      */
getOptimizationLevel()225     public int getOptimizationLevel() {
226         return mOptimizationLevel;
227     }
228 
229     /**
230      * Returns true if the given feature should be optimized
231      */
optimizeFor(int feature)232     public boolean optimizeFor(int feature) {
233         return (mOptimizationLevel & feature) == feature;
234     }
235 
236     /**
237      * Specify the xml type for the container
238      */
239     @Override
getType()240     public String getType() {
241         return "ConstraintLayout";
242     }
243 
244     @Override
reset()245     public void reset() {
246         mSystem.reset();
247         mPaddingLeft = 0;
248         mPaddingRight = 0;
249         mPaddingTop = 0;
250         mPaddingBottom = 0;
251         mSkipSolver = false;
252         super.reset();
253     }
254 
255     /**
256      * Return true if the width given is too small for the content laid out
257      */
isWidthMeasuredTooSmall()258     public boolean isWidthMeasuredTooSmall() {
259         return mWidthMeasuredTooSmall;
260     }
261 
262     /**
263      * Return true if the height given is too small for the content laid out
264      */
isHeightMeasuredTooSmall()265     public boolean isHeightMeasuredTooSmall() {
266         return mHeightMeasuredTooSmall;
267     }
268 
269     int mDebugSolverPassCount = 0;
270 
271     private WeakReference<ConstraintAnchor> mVerticalWrapMin = null;
272     private WeakReference<ConstraintAnchor> mHorizontalWrapMin = null;
273     private WeakReference<ConstraintAnchor> mVerticalWrapMax = null;
274     private WeakReference<ConstraintAnchor> mHorizontalWrapMax = null;
275 
addVerticalWrapMinVariable(ConstraintAnchor top)276     void addVerticalWrapMinVariable(ConstraintAnchor top) {
277         if (mVerticalWrapMin == null || mVerticalWrapMin.get() == null
278                 || top.getFinalValue() > mVerticalWrapMin.get().getFinalValue()) {
279             mVerticalWrapMin = new WeakReference<>(top);
280         }
281     }
282 
283     // @TODO: add description
addHorizontalWrapMinVariable(ConstraintAnchor left)284     public void addHorizontalWrapMinVariable(ConstraintAnchor left) {
285         if (mHorizontalWrapMin == null || mHorizontalWrapMin.get() == null
286                 || left.getFinalValue() > mHorizontalWrapMin.get().getFinalValue()) {
287             mHorizontalWrapMin = new WeakReference<>(left);
288         }
289     }
290 
addVerticalWrapMaxVariable(ConstraintAnchor bottom)291     void addVerticalWrapMaxVariable(ConstraintAnchor bottom) {
292         if (mVerticalWrapMax == null || mVerticalWrapMax.get() == null
293                 || bottom.getFinalValue() > mVerticalWrapMax.get().getFinalValue()) {
294             mVerticalWrapMax = new WeakReference<>(bottom);
295         }
296     }
297 
298     // @TODO: add description
addHorizontalWrapMaxVariable(ConstraintAnchor right)299     public void addHorizontalWrapMaxVariable(ConstraintAnchor right) {
300         if (mHorizontalWrapMax == null || mHorizontalWrapMax.get() == null
301                 || right.getFinalValue() > mHorizontalWrapMax.get().getFinalValue()) {
302             mHorizontalWrapMax = new WeakReference<>(right);
303         }
304     }
305 
addMinWrap(ConstraintAnchor constraintAnchor, SolverVariable parentMin)306     private void addMinWrap(ConstraintAnchor constraintAnchor, SolverVariable parentMin) {
307         SolverVariable variable = mSystem.createObjectVariable(constraintAnchor);
308         int wrapStrength = SolverVariable.STRENGTH_EQUALITY;
309         mSystem.addGreaterThan(variable, parentMin, 0, wrapStrength);
310     }
311 
addMaxWrap(ConstraintAnchor constraintAnchor, SolverVariable parentMax)312     private void addMaxWrap(ConstraintAnchor constraintAnchor, SolverVariable parentMax) {
313         SolverVariable variable = mSystem.createObjectVariable(constraintAnchor);
314         int wrapStrength = SolverVariable.STRENGTH_EQUALITY;
315         mSystem.addGreaterThan(parentMax, variable, 0, wrapStrength);
316     }
317 
318     HashSet<ConstraintWidget> mWidgetsToAdd = new HashSet<>();
319 
320     /**
321      * Add this widget to the solver
322      *
323      * @param system the solver we want to add the widget to
324      */
addChildrenToSolver(LinearSystem system)325     public boolean addChildrenToSolver(LinearSystem system) {
326         if (DEBUG) {
327             System.out.println("\n#######################################");
328             System.out.println("##    ADD CHILDREN TO SOLVER  (" + mDebugSolverPassCount + ") ##");
329             System.out.println("#######################################\n");
330             mDebugSolverPassCount++;
331         }
332 
333         boolean optimize = optimizeFor(Optimizer.OPTIMIZATION_GRAPH);
334         addToSolver(system, optimize);
335         final int count = mChildren.size();
336 
337         boolean hasBarriers = false;
338         for (int i = 0; i < count; i++) {
339             ConstraintWidget widget = mChildren.get(i);
340             widget.setInBarrier(HORIZONTAL, false);
341             widget.setInBarrier(VERTICAL, false);
342             if (widget instanceof Barrier) {
343                 hasBarriers = true;
344             }
345         }
346 
347         if (hasBarriers) {
348             for (int i = 0; i < count; i++) {
349                 ConstraintWidget widget = mChildren.get(i);
350                 if (widget instanceof Barrier) {
351                     ((Barrier) widget).markWidgets();
352                 }
353             }
354         }
355 
356         mWidgetsToAdd.clear();
357         for (int i = 0; i < count; i++) {
358             ConstraintWidget widget = mChildren.get(i);
359             if (widget.addFirst()) {
360                 if (widget instanceof VirtualLayout) {
361                     mWidgetsToAdd.add(widget);
362                 } else {
363                     widget.addToSolver(system, optimize);
364                 }
365             }
366         }
367 
368         // If we have virtual layouts, we need to add them to the solver in the correct
369         // order (in case they reference one another)
370         while (mWidgetsToAdd.size() > 0) {
371             int numLayouts = mWidgetsToAdd.size();
372             VirtualLayout layout = null;
373             for (ConstraintWidget widget : mWidgetsToAdd) {
374                 layout = (VirtualLayout) widget;
375 
376                 // we'll go through the virtual layouts that references others first, to give
377                 // them a shot at setting their constraints.
378                 if (layout.contains(mWidgetsToAdd)) {
379                     layout.addToSolver(system, optimize);
380                     mWidgetsToAdd.remove(layout);
381                     break;
382                 }
383             }
384             if (numLayouts == mWidgetsToAdd.size()) {
385                 // looks we didn't find anymore dependency, let's add everything.
386                 for (ConstraintWidget widget : mWidgetsToAdd) {
387                     widget.addToSolver(system, optimize);
388                 }
389                 mWidgetsToAdd.clear();
390             }
391         }
392 
393         if (LinearSystem.USE_DEPENDENCY_ORDERING) {
394             HashSet<ConstraintWidget> widgetsToAdd = new HashSet<>();
395             for (int i = 0; i < count; i++) {
396                 ConstraintWidget widget = mChildren.get(i);
397                 if (!widget.addFirst()) {
398                     widgetsToAdd.add(widget);
399                 }
400             }
401             int orientation = VERTICAL;
402             if (getHorizontalDimensionBehaviour() == WRAP_CONTENT) {
403                 orientation = HORIZONTAL;
404             }
405             addChildrenToSolverByDependency(this, system, widgetsToAdd, orientation, false);
406             for (ConstraintWidget widget : widgetsToAdd) {
407                 Optimizer.checkMatchParent(this, system, widget);
408                 widget.addToSolver(system, optimize);
409             }
410         } else {
411 
412             for (int i = 0; i < count; i++) {
413                 ConstraintWidget widget = mChildren.get(i);
414                 if (widget instanceof ConstraintWidgetContainer) {
415                     DimensionBehaviour horizontalBehaviour =
416                             widget.mListDimensionBehaviors[DIMENSION_HORIZONTAL];
417                     DimensionBehaviour verticalBehaviour =
418                             widget.mListDimensionBehaviors[DIMENSION_VERTICAL];
419                     if (horizontalBehaviour == WRAP_CONTENT) {
420                         widget.setHorizontalDimensionBehaviour(FIXED);
421                     }
422                     if (verticalBehaviour == WRAP_CONTENT) {
423                         widget.setVerticalDimensionBehaviour(FIXED);
424                     }
425                     widget.addToSolver(system, optimize);
426                     if (horizontalBehaviour == WRAP_CONTENT) {
427                         widget.setHorizontalDimensionBehaviour(horizontalBehaviour);
428                     }
429                     if (verticalBehaviour == WRAP_CONTENT) {
430                         widget.setVerticalDimensionBehaviour(verticalBehaviour);
431                     }
432                 } else {
433                     Optimizer.checkMatchParent(this, system, widget);
434                     if (!widget.addFirst()) {
435                         widget.addToSolver(system, optimize);
436                     }
437                 }
438             }
439         }
440 
441         if (mHorizontalChainsSize > 0) {
442             Chain.applyChainConstraints(this, system, null, HORIZONTAL);
443         }
444         if (mVerticalChainsSize > 0) {
445             Chain.applyChainConstraints(this, system, null, VERTICAL);
446         }
447         return true;
448     }
449 
450     /**
451      * Update the frame of the layout and its children from the solver
452      *
453      * @param system the solver we get the values from.
454      * @param flags  the flag set associated with this solver
455      */
updateChildrenFromSolver(LinearSystem system, boolean[] flags)456     public boolean updateChildrenFromSolver(LinearSystem system, boolean[] flags) {
457         flags[Optimizer.FLAG_RECOMPUTE_BOUNDS] = false;
458         boolean optimize = optimizeFor(Optimizer.OPTIMIZATION_GRAPH);
459         updateFromSolver(system, optimize);
460         final int count = mChildren.size();
461         boolean hasOverride = false;
462         for (int i = 0; i < count; i++) {
463             ConstraintWidget widget = mChildren.get(i);
464             widget.updateFromSolver(system, optimize);
465             if (widget.hasDimensionOverride()) {
466                 hasOverride = true;
467             }
468         }
469         return hasOverride;
470     }
471 
472     @Override
updateFromRuns(boolean updateHorizontal, boolean updateVertical)473     public void updateFromRuns(boolean updateHorizontal, boolean updateVertical) {
474         super.updateFromRuns(updateHorizontal, updateVertical);
475         final int count = mChildren.size();
476         for (int i = 0; i < count; i++) {
477             ConstraintWidget widget = mChildren.get(i);
478             widget.updateFromRuns(updateHorizontal, updateVertical);
479         }
480     }
481 
482     /**
483      * Set the padding on this container. It will apply to the position of the children.
484      *
485      * @param left   left padding
486      * @param top    top padding
487      * @param right  right padding
488      * @param bottom bottom padding
489      */
setPadding(int left, int top, int right, int bottom)490     public void setPadding(int left, int top, int right, int bottom) {
491         mPaddingLeft = left;
492         mPaddingTop = top;
493         mPaddingRight = right;
494         mPaddingBottom = bottom;
495     }
496 
497     /**
498      * Set the rtl status. This has implications for Chains.
499      *
500      * @param isRtl true if we are in RTL.
501      */
setRtl(boolean isRtl)502     public void setRtl(boolean isRtl) {
503         mIsRtl = isRtl;
504     }
505 
506     /**
507      * Returns the rtl status.
508      *
509      * @return true if in RTL, false otherwise.
510      */
isRtl()511     public boolean isRtl() {
512         return mIsRtl;
513     }
514 
515     /*-----------------------------------------------------------------------*/
516     // Overloaded methods from ConstraintWidget
517     /*-----------------------------------------------------------------------*/
518 
519     public BasicMeasure.Measure mMeasure = new BasicMeasure.Measure();
520 
521     // @TODO: add description
measure(int level, ConstraintWidget widget, BasicMeasure.Measurer measurer, BasicMeasure.Measure measure, int measureStrategy)522     public static boolean measure(int level,
523             ConstraintWidget widget,
524             BasicMeasure.Measurer measurer,
525             BasicMeasure.Measure measure,
526             int measureStrategy) {
527         if (DEBUG) {
528             System.out.println(Direct.ls(level) + "(M) call to measure " + widget.getDebugName());
529         }
530         if (measurer == null) {
531             return false;
532         }
533         if (widget.getVisibility() == GONE
534                 || widget instanceof Guideline
535                 || widget instanceof Barrier) {
536             if (DEBUG) {
537                 System.out.println(Direct.ls(level)
538                         + "(M) no measure needed for " + widget.getDebugName());
539             }
540             measure.measuredWidth = 0;
541             measure.measuredHeight = 0;
542             return false;
543         }
544 
545         measure.horizontalBehavior = widget.getHorizontalDimensionBehaviour();
546         measure.verticalBehavior = widget.getVerticalDimensionBehaviour();
547         measure.horizontalDimension = widget.getWidth();
548         measure.verticalDimension = widget.getHeight();
549         measure.measuredNeedsSolverPass = false;
550         measure.measureStrategy = measureStrategy;
551 
552         boolean horizontalMatchConstraints =
553                 (measure.horizontalBehavior == DimensionBehaviour.MATCH_CONSTRAINT);
554         boolean verticalMatchConstraints =
555                 (measure.verticalBehavior == DimensionBehaviour.MATCH_CONSTRAINT);
556 
557         boolean horizontalUseRatio = horizontalMatchConstraints && widget.mDimensionRatio > 0;
558         boolean verticalUseRatio = verticalMatchConstraints && widget.mDimensionRatio > 0;
559 
560         if (horizontalMatchConstraints && widget.hasDanglingDimension(HORIZONTAL)
561                 && widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD
562                 && !horizontalUseRatio) {
563             horizontalMatchConstraints = false;
564             measure.horizontalBehavior = WRAP_CONTENT;
565             if (verticalMatchConstraints
566                     && widget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD) {
567                 // if match x match, size would be zero.
568                 measure.horizontalBehavior = FIXED;
569             }
570         }
571 
572         if (verticalMatchConstraints && widget.hasDanglingDimension(VERTICAL)
573                 && widget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD
574                 && !verticalUseRatio) {
575             verticalMatchConstraints = false;
576             measure.verticalBehavior = WRAP_CONTENT;
577             if (horizontalMatchConstraints
578                     && widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD) {
579                 // if match x match, size would be zero.
580                 measure.verticalBehavior = FIXED;
581             }
582         }
583 
584         if (widget.isResolvedHorizontally()) {
585             horizontalMatchConstraints = false;
586             measure.horizontalBehavior = FIXED;
587         }
588         if (widget.isResolvedVertically()) {
589             verticalMatchConstraints = false;
590             measure.verticalBehavior = FIXED;
591         }
592 
593         if (horizontalUseRatio) {
594             if (widget.mResolvedMatchConstraintDefault[HORIZONTAL]
595                     == ConstraintWidget.MATCH_CONSTRAINT_RATIO_RESOLVED) {
596                 measure.horizontalBehavior = FIXED;
597             } else if (!verticalMatchConstraints) {
598                 // let's measure here
599                 int measuredHeight;
600                 if (measure.verticalBehavior == FIXED) {
601                     measuredHeight = measure.verticalDimension;
602                 } else {
603                     measure.horizontalBehavior = WRAP_CONTENT;
604                     measurer.measure(widget, measure);
605                     measuredHeight = measure.measuredHeight;
606                 }
607                 measure.horizontalBehavior = FIXED;
608                 // regardless of which side we are using for the ratio, getDimensionRatio() already
609                 // made sure that it's expressed in WxH format, so we can simply go and multiply
610                 measure.horizontalDimension = (int) (widget.getDimensionRatio() * measuredHeight);
611                 if (DEBUG) {
612                     System.out.println("(M) Measured once for ratio on horizontal side...");
613                 }
614             }
615         }
616         if (verticalUseRatio) {
617             if (widget.mResolvedMatchConstraintDefault[VERTICAL]
618                     == ConstraintWidget.MATCH_CONSTRAINT_RATIO_RESOLVED) {
619                 measure.verticalBehavior = FIXED;
620             } else if (!horizontalMatchConstraints) {
621                 // let's measure here
622                 int measuredWidth;
623                 if (measure.horizontalBehavior == FIXED) {
624                     measuredWidth = measure.horizontalDimension;
625                 } else {
626                     measure.verticalBehavior = WRAP_CONTENT;
627                     measurer.measure(widget, measure);
628                     measuredWidth = measure.measuredWidth;
629                 }
630                 measure.verticalBehavior = FIXED;
631                 if (widget.getDimensionRatioSide() == -1) {
632                     // regardless of which side we are using for the ratio,
633                     //  getDimensionRatio() already
634                     // made sure that it's expressed in WxH format,
635                     //  so we can simply go and divide
636                     measure.verticalDimension = (int) (measuredWidth / widget.getDimensionRatio());
637                 } else {
638                     // getDimensionRatio() already got reverted, so we can simply multiply
639                     measure.verticalDimension = (int) (widget.getDimensionRatio() * measuredWidth);
640                 }
641                 if (DEBUG) {
642                     System.out.println("(M) Measured once for ratio on vertical side...");
643                 }
644             }
645         }
646 
647         measurer.measure(widget, measure);
648         widget.setWidth(measure.measuredWidth);
649         widget.setHeight(measure.measuredHeight);
650         widget.setHasBaseline(measure.measuredHasBaseline);
651         widget.setBaselineDistance(measure.measuredBaseline);
652         measure.measureStrategy = BasicMeasure.Measure.SELF_DIMENSIONS;
653         if (DEBUG) {
654             System.out.println("(M) Measured " + widget.getDebugName() + " with : "
655                     + widget.getHorizontalDimensionBehaviour() + " x "
656                     + widget.getVerticalDimensionBehaviour() + " => "
657                     + widget.getWidth() + " x " + widget.getHeight());
658         }
659         return measure.measuredNeedsSolverPass;
660     }
661 
662     static int sMyCounter = 0;
663 
664     /**
665      * Layout the tree of widgets
666      */
667     @Override
layout()668     public void layout() {
669         if (DEBUG) {
670             System.out.println("\n#####################################");
671             System.out.println("##          CL LAYOUT PASS           ##");
672             System.out.println("#####################################\n");
673             mDebugSolverPassCount = 0;
674         }
675 
676         mX = 0;
677         mY = 0;
678 
679         mWidthMeasuredTooSmall = false;
680         mHeightMeasuredTooSmall = false;
681         final int count = mChildren.size();
682 
683         int preW = Math.max(0, getWidth());
684         int preH = Math.max(0, getHeight());
685         DimensionBehaviour originalVerticalDimensionBehaviour =
686                 mListDimensionBehaviors[DIMENSION_VERTICAL];
687         DimensionBehaviour originalHorizontalDimensionBehaviour =
688                 mListDimensionBehaviors[DIMENSION_HORIZONTAL];
689 
690         if (DEBUG_LAYOUT) {
691             System.out.println("layout with preW: " + preW + " ("
692                     + mListDimensionBehaviors[DIMENSION_HORIZONTAL] + ") preH: " + preH
693                     + " (" + mListDimensionBehaviors[DIMENSION_VERTICAL] + ")");
694         }
695 
696         if (mMetrics != null) {
697             mMetrics.layouts++;
698         }
699 
700 
701         boolean wrap_override = false;
702 
703         if (FULL_DEBUG) {
704             System.out.println("OPTIMIZATION LEVEL " + mOptimizationLevel);
705         }
706 
707         // Only try the direct optimization in the first layout pass
708         if (mPass == 0 && Optimizer.enabled(mOptimizationLevel, Optimizer.OPTIMIZATION_DIRECT)) {
709             if (FULL_DEBUG) {
710                 System.out.println("Direct pass " + sMyCounter++);
711             }
712             Direct.solvingPass(this, getMeasurer());
713             if (FULL_DEBUG) {
714                 System.out.println("Direct pass done.");
715             }
716             for (int i = 0; i < count; i++) {
717                 ConstraintWidget child = mChildren.get(i);
718                 if (FULL_DEBUG) {
719                     if (child.isInHorizontalChain()) {
720                         System.out.print("H");
721                     } else {
722                         System.out.print(" ");
723                     }
724                     if (child.isInVerticalChain()) {
725                         System.out.print("V");
726                     } else {
727                         System.out.print(" ");
728                     }
729                     if (child.isResolvedHorizontally() && child.isResolvedVertically()) {
730                         System.out.print("*");
731                     } else {
732                         System.out.print(" ");
733                     }
734                     System.out.println("[" + i + "] child " + child.getDebugName()
735                             + " H: " + child.isResolvedHorizontally()
736                             + " V: " + child.isResolvedVertically());
737                 }
738                 if (child.isMeasureRequested()
739                         && !(child instanceof Guideline)
740                         && !(child instanceof Barrier)
741                         && !(child instanceof VirtualLayout)
742                         && !child.isInVirtualLayout()) {
743                     DimensionBehaviour widthBehavior = child.getDimensionBehaviour(HORIZONTAL);
744                     DimensionBehaviour heightBehavior = child.getDimensionBehaviour(VERTICAL);
745 
746                     boolean skip = widthBehavior == DimensionBehaviour.MATCH_CONSTRAINT
747                             && child.mMatchConstraintDefaultWidth != MATCH_CONSTRAINT_WRAP
748                             && heightBehavior == DimensionBehaviour.MATCH_CONSTRAINT
749                             && child.mMatchConstraintDefaultHeight != MATCH_CONSTRAINT_WRAP;
750                     if (!skip) {
751                         BasicMeasure.Measure measure = new BasicMeasure.Measure();
752                         ConstraintWidgetContainer.measure(0, child, mMeasurer,
753                                 measure, BasicMeasure.Measure.SELF_DIMENSIONS);
754                     }
755                 }
756             }
757             // let's measure children
758             if (FULL_DEBUG) {
759                 System.out.println("Direct pass all done.");
760             }
761         } else {
762             if (FULL_DEBUG) {
763                 System.out.println("No DIRECT PASS");
764             }
765         }
766 
767         if (count > 2 && (originalHorizontalDimensionBehaviour == WRAP_CONTENT
768                 || originalVerticalDimensionBehaviour == WRAP_CONTENT)
769                 && Optimizer.enabled(mOptimizationLevel, Optimizer.OPTIMIZATION_GROUPING)) {
770             if (Grouping.simpleSolvingPass(this, getMeasurer())) {
771                 if (originalHorizontalDimensionBehaviour == WRAP_CONTENT) {
772                     if (preW < getWidth() && preW > 0) {
773                         if (DEBUG_LAYOUT) {
774                             System.out.println("Override width " + getWidth() + " to " + preH);
775                         }
776                         setWidth(preW);
777                         mWidthMeasuredTooSmall = true;
778                     } else {
779                         preW = getWidth();
780                     }
781                 }
782                 if (originalVerticalDimensionBehaviour == WRAP_CONTENT) {
783                     if (preH < getHeight() && preH > 0) {
784                         if (DEBUG_LAYOUT) {
785                             System.out.println("Override height " + getHeight() + " to " + preH);
786                         }
787                         setHeight(preH);
788                         mHeightMeasuredTooSmall = true;
789                     } else {
790                         preH = getHeight();
791                     }
792                 }
793                 wrap_override = true;
794                 if (DEBUG_LAYOUT) {
795                     System.out.println("layout post opt, preW: " + preW
796                             + " (" + mListDimensionBehaviors[DIMENSION_HORIZONTAL]
797                             + ") preH: " + preH + " (" + mListDimensionBehaviors[DIMENSION_VERTICAL]
798                             + "), new size " + getWidth() + " x " + getHeight());
799                 }
800             }
801         }
802         boolean useGraphOptimizer = optimizeFor(Optimizer.OPTIMIZATION_GRAPH)
803                 || optimizeFor(Optimizer.OPTIMIZATION_GRAPH_WRAP);
804 
805         mSystem.graphOptimizer = false;
806         mSystem.newgraphOptimizer = false;
807 
808         if (mOptimizationLevel != Optimizer.OPTIMIZATION_NONE
809                 && useGraphOptimizer) {
810             mSystem.newgraphOptimizer = true;
811         }
812 
813         @SuppressWarnings("unused") int countSolve = 0;
814         final List<ConstraintWidget> allChildren = mChildren;
815         boolean hasWrapContent = getHorizontalDimensionBehaviour() == WRAP_CONTENT
816                 || getVerticalDimensionBehaviour() == WRAP_CONTENT;
817 
818         // Reset the chains before iterating on our children
819         resetChains();
820         countSolve = 0;
821 
822         // Before we solve our system, we should call layout() on any
823         // of our children that is a container.
824         for (int i = 0; i < count; i++) {
825             ConstraintWidget widget = mChildren.get(i);
826             if (widget instanceof WidgetContainer) {
827                 ((WidgetContainer) widget).layout();
828             }
829         }
830         boolean optimize = optimizeFor(Optimizer.OPTIMIZATION_GRAPH);
831 
832         // Now let's solve our system as usual
833         boolean needsSolving = true;
834         while (needsSolving) {
835             countSolve++;
836             try {
837                 mSystem.reset();
838                 resetChains();
839                 if (DEBUG) {
840                     String debugName = getDebugName();
841                     if (debugName == null) {
842                         debugName = "root";
843                     }
844                     setDebugSolverName(mSystem, debugName);
845                     for (int i = 0; i < count; i++) {
846                         ConstraintWidget widget = mChildren.get(i);
847                         if (widget.getDebugName() != null) {
848                             widget.setDebugSolverName(mSystem, widget.getDebugName());
849                         }
850                     }
851                 } else {
852                     createObjectVariables(mSystem);
853                     for (int i = 0; i < count; i++) {
854                         ConstraintWidget widget = mChildren.get(i);
855                         widget.createObjectVariables(mSystem);
856                     }
857                 }
858                 needsSolving = addChildrenToSolver(mSystem);
859                 if (mVerticalWrapMin != null && mVerticalWrapMin.get() != null) {
860                     addMinWrap(mVerticalWrapMin.get(), mSystem.createObjectVariable(mTop));
861                     mVerticalWrapMin = null;
862                 }
863                 if (mVerticalWrapMax != null && mVerticalWrapMax.get() != null) {
864                     addMaxWrap(mVerticalWrapMax.get(), mSystem.createObjectVariable(mBottom));
865                     mVerticalWrapMax = null;
866                 }
867                 if (mHorizontalWrapMin != null && mHorizontalWrapMin.get() != null) {
868                     addMinWrap(mHorizontalWrapMin.get(), mSystem.createObjectVariable(mLeft));
869                     mHorizontalWrapMin = null;
870                 }
871                 if (mHorizontalWrapMax != null && mHorizontalWrapMax.get() != null) {
872                     addMaxWrap(mHorizontalWrapMax.get(), mSystem.createObjectVariable(mRight));
873                     mHorizontalWrapMax = null;
874                 }
875                 if (needsSolving) {
876                     mSystem.minimize();
877                 }
878             } catch (Exception e) {
879                 e.printStackTrace();
880                 System.out.println("EXCEPTION : " + e);
881             }
882             if (needsSolving) {
883                 needsSolving = updateChildrenFromSolver(mSystem, Optimizer.sFlags);
884             } else {
885                 updateFromSolver(mSystem, optimize);
886                 for (int i = 0; i < count; i++) {
887                     ConstraintWidget widget = mChildren.get(i);
888                     widget.updateFromSolver(mSystem, optimize);
889                 }
890                 needsSolving = false;
891             }
892 
893             if (hasWrapContent && countSolve < MAX_ITERATIONS
894                     && Optimizer.sFlags[Optimizer.FLAG_RECOMPUTE_BOUNDS]) {
895                 // let's get the new bounds
896                 int maxX = 0;
897                 int maxY = 0;
898                 for (int i = 0; i < count; i++) {
899                     ConstraintWidget widget = mChildren.get(i);
900                     maxX = Math.max(maxX, widget.mX + widget.getWidth());
901                     maxY = Math.max(maxY, widget.mY + widget.getHeight());
902                 }
903                 maxX = Math.max(mMinWidth, maxX);
904                 maxY = Math.max(mMinHeight, maxY);
905                 if (originalHorizontalDimensionBehaviour == WRAP_CONTENT) {
906                     if (getWidth() < maxX) {
907                         if (DEBUG_LAYOUT) {
908                             System.out.println( countSolve +
909                                     "layout override width from " + getWidth() + " vs " + maxX);
910                         }
911                         setWidth(maxX);
912                         // force using the solver
913                         mListDimensionBehaviors[DIMENSION_HORIZONTAL] = WRAP_CONTENT;
914                         wrap_override = true;
915                         needsSolving = true;
916                     }
917                 }
918                 if (originalVerticalDimensionBehaviour == WRAP_CONTENT) {
919                     if (getHeight() < maxY) {
920                         if (DEBUG_LAYOUT) {
921                             System.out.println(
922                                     "layout override height from " + getHeight() + " vs " + maxY);
923                         }
924                         setHeight(maxY);
925                         // force using the solver
926                         mListDimensionBehaviors[DIMENSION_VERTICAL] = WRAP_CONTENT;
927                         wrap_override = true;
928                         needsSolving = true;
929                     }
930                 }
931             }
932             if (true) {
933                 int width = Math.max(mMinWidth, getWidth());
934                 if (width > getWidth()) {
935                     if (DEBUG_LAYOUT) {
936                         System.out.println(
937                                 "layout override 2, width from " + getWidth() + " vs " + width);
938                     }
939                     setWidth(width);
940                     mListDimensionBehaviors[DIMENSION_HORIZONTAL] = FIXED;
941                     wrap_override = true;
942                     needsSolving = true;
943                 }
944                 int height = Math.max(mMinHeight, getHeight());
945                 if (height > getHeight()) {
946                     if (DEBUG_LAYOUT) {
947                         System.out.println(
948                                 "layout override 2, height from " + getHeight() + " vs " + height);
949                     }
950                     setHeight(height);
951                     mListDimensionBehaviors[DIMENSION_VERTICAL] = FIXED;
952                     wrap_override = true;
953                     needsSolving = true;
954                 }
955 
956                 if (!wrap_override) {
957                     if (mListDimensionBehaviors[DIMENSION_HORIZONTAL] == WRAP_CONTENT
958                             && preW > 0) {
959                         if (getWidth() > preW) {
960                             if (DEBUG_LAYOUT) {
961                                 System.out.println(
962                                         "layout override 3, width from " + getWidth() + " vs "
963                                                 + preW);
964                             }
965                             mWidthMeasuredTooSmall = true;
966                             wrap_override = true;
967                             mListDimensionBehaviors[DIMENSION_HORIZONTAL] = FIXED;
968                             setWidth(preW);
969                             needsSolving = true;
970                         }
971                     }
972                     if (mListDimensionBehaviors[DIMENSION_VERTICAL] == WRAP_CONTENT
973                             && preH > 0) {
974                         if (getHeight() > preH) {
975                             if (DEBUG_LAYOUT) {
976                                 System.out.println(
977                                         "layout override 3, height from " + getHeight() + " vs "
978                                                 + preH);
979                             }
980                             mHeightMeasuredTooSmall = true;
981                             wrap_override = true;
982                             mListDimensionBehaviors[DIMENSION_VERTICAL] = FIXED;
983                             setHeight(preH);
984                             needsSolving = true;
985                         }
986                     }
987                 }
988 
989                 if (countSolve > MAX_ITERATIONS) {
990                     needsSolving = false;
991                 }
992             }
993         }
994         if (DEBUG_LAYOUT) {
995             System.out.println(
996                     "Solved system in " + countSolve + " iterations (" + getWidth() + " x "
997                             + getHeight() + ")");
998         }
999 
1000         mChildren = (ArrayList<ConstraintWidget>) allChildren;
1001 
1002         if (wrap_override) {
1003             mListDimensionBehaviors[DIMENSION_HORIZONTAL] = originalHorizontalDimensionBehaviour;
1004             mListDimensionBehaviors[DIMENSION_VERTICAL] = originalVerticalDimensionBehaviour;
1005         }
1006 
1007         resetSolverVariables(mSystem.getCache());
1008     }
1009 
1010     /**
1011      * Indicates if the container knows how to layout its content on its own
1012      *
1013      * @return true if the container does the layout, false otherwise
1014      */
handlesInternalConstraints()1015     public boolean handlesInternalConstraints() {
1016         return false;
1017     }
1018 
1019     /*-----------------------------------------------------------------------*/
1020     // Guidelines
1021     /*-----------------------------------------------------------------------*/
1022 
1023     /**
1024      * Accessor to the vertical guidelines contained in the table.
1025      *
1026      * @return array of guidelines
1027      */
getVerticalGuidelines()1028     public ArrayList<Guideline> getVerticalGuidelines() {
1029         ArrayList<Guideline> guidelines = new ArrayList<>();
1030         for (int i = 0, mChildrenSize = mChildren.size(); i < mChildrenSize; i++) {
1031             final ConstraintWidget widget = mChildren.get(i);
1032             if (widget instanceof Guideline) {
1033                 Guideline guideline = (Guideline) widget;
1034                 if (guideline.getOrientation() == Guideline.VERTICAL) {
1035                     guidelines.add(guideline);
1036                 }
1037             }
1038         }
1039         return guidelines;
1040     }
1041 
1042     /**
1043      * Accessor to the horizontal guidelines contained in the table.
1044      *
1045      * @return array of guidelines
1046      */
getHorizontalGuidelines()1047     public ArrayList<Guideline> getHorizontalGuidelines() {
1048         ArrayList<Guideline> guidelines = new ArrayList<>();
1049         for (int i = 0, mChildrenSize = mChildren.size(); i < mChildrenSize; i++) {
1050             final ConstraintWidget widget = mChildren.get(i);
1051             if (widget instanceof Guideline) {
1052                 Guideline guideline = (Guideline) widget;
1053                 if (guideline.getOrientation() == Guideline.HORIZONTAL) {
1054                     guidelines.add(guideline);
1055                 }
1056             }
1057         }
1058         return guidelines;
1059     }
1060 
getSystem()1061     public LinearSystem getSystem() {
1062         return mSystem;
1063     }
1064 
1065     /*-----------------------------------------------------------------------*/
1066     // Chains
1067     /*-----------------------------------------------------------------------*/
1068 
1069     /**
1070      * Reset the chains array. Need to be called before layout.
1071      */
resetChains()1072     private void resetChains() {
1073         mHorizontalChainsSize = 0;
1074         mVerticalChainsSize = 0;
1075     }
1076 
1077     /**
1078      * Add the chain which constraintWidget is part of. Called by ConstraintWidget::addToSolver()
1079      *
1080      * @param type HORIZONTAL or VERTICAL chain
1081      */
addChain(ConstraintWidget constraintWidget, int type)1082     void addChain(ConstraintWidget constraintWidget, int type) {
1083         ConstraintWidget widget = constraintWidget;
1084         if (type == HORIZONTAL) {
1085             addHorizontalChain(widget);
1086         } else if (type == VERTICAL) {
1087             addVerticalChain(widget);
1088         }
1089     }
1090 
1091     /**
1092      * Add a widget to the list of horizontal chains. The widget is the left-most widget
1093      * of the chain which doesn't have a left dual connection.
1094      *
1095      * @param widget widget starting the chain
1096      */
addHorizontalChain(ConstraintWidget widget)1097     private void addHorizontalChain(ConstraintWidget widget) {
1098         if (mHorizontalChainsSize + 1 >= mHorizontalChainsArray.length) {
1099             mHorizontalChainsArray = Arrays
1100                     .copyOf(mHorizontalChainsArray, mHorizontalChainsArray.length * 2);
1101         }
1102         mHorizontalChainsArray[mHorizontalChainsSize] = new ChainHead(widget, HORIZONTAL, isRtl());
1103         mHorizontalChainsSize++;
1104     }
1105 
1106     /**
1107      * Add a widget to the list of vertical chains. The widget is the top-most widget
1108      * of the chain which doesn't have a top dual connection.
1109      *
1110      * @param widget widget starting the chain
1111      */
addVerticalChain(ConstraintWidget widget)1112     private void addVerticalChain(ConstraintWidget widget) {
1113         if (mVerticalChainsSize + 1 >= mVerticalChainsArray.length) {
1114             mVerticalChainsArray = Arrays
1115                     .copyOf(mVerticalChainsArray, mVerticalChainsArray.length * 2);
1116         }
1117         mVerticalChainsArray[mVerticalChainsSize] = new ChainHead(widget, VERTICAL, isRtl());
1118         mVerticalChainsSize++;
1119     }
1120 
1121     /**
1122      * Keep track of the # of passes
1123      */
setPass(int pass)1124     public void setPass(int pass) {
1125         this.mPass = pass;
1126     }
1127 
1128     // @TODO: add description
1129     @Override
getSceneString(StringBuilder ret)1130     public void getSceneString(StringBuilder ret) {
1131 
1132         ret.append(stringId + ":{\n");
1133         ret.append("  actualWidth:" + mWidth);
1134         ret.append("\n");
1135         ret.append("  actualHeight:" + mHeight);
1136         ret.append("\n");
1137 
1138         ArrayList<ConstraintWidget> children = getChildren();
1139         for (ConstraintWidget child : children) {
1140             child.getSceneString(ret);
1141             ret.append(",\n");
1142         }
1143         ret.append("}");
1144 
1145     }
1146 }
1147