/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.constraintlayout.core.widgets;
import androidx.constraintlayout.core.LinearSystem;
import androidx.constraintlayout.core.Metrics;
import androidx.constraintlayout.core.SolverVariable;
import androidx.constraintlayout.core.widgets.analyzer.BasicMeasure;
import androidx.constraintlayout.core.widgets.analyzer.DependencyGraph;
import androidx.constraintlayout.core.widgets.analyzer.Direct;
import androidx.constraintlayout.core.widgets.analyzer.Grouping;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import static androidx.constraintlayout.core.LinearSystem.FULL_DEBUG;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.FIXED;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.WRAP_CONTENT;
/**
* A container of ConstraintWidget that can layout its children
*/
public class ConstraintWidgetContainer extends WidgetContainer {
private static final int MAX_ITERATIONS = 8;
private static final boolean DEBUG = FULL_DEBUG;
private static final boolean DEBUG_LAYOUT = false;
static final boolean DEBUG_GRAPH = false;
BasicMeasure mBasicMeasureSolver = new BasicMeasure(this);
////////////////////////////////////////////////////////////////////////////////////////////////
// Graph measures
////////////////////////////////////////////////////////////////////////////////////////////////
public DependencyGraph mDependencyGraph = new DependencyGraph(this);
private int pass; // number of layout passes
/**
* Invalidate the graph of constraints
*/
public void invalidateGraph() {
mDependencyGraph.invalidateGraph();
}
/**
* Invalidate the widgets measures
*/
public void invalidateMeasures() {
mDependencyGraph.invalidateMeasures();
}
public boolean directMeasure(boolean optimizeWrap) {
return mDependencyGraph.directMeasure(optimizeWrap);
// int paddingLeft = getX();
// int paddingTop = getY();
// if (mDependencyGraph.directMeasureSetup(optimizeWrap)) {
// mDependencyGraph.measureWidgets();
// boolean allResolved = mDependencyGraph.directMeasureWithOrientation(optimizeWrap, HORIZONTAL);
// allResolved &= mDependencyGraph.directMeasureWithOrientation(optimizeWrap, VERTICAL);
// for (ConstraintWidget child : mChildren) {
// child.setDrawX(child.getDrawX() + paddingLeft);
// child.setDrawY(child.getDrawY() + paddingTop);
// }
// setX(paddingLeft);
// setY(paddingTop);
// return allResolved;
// }
// return false;
}
public boolean directMeasureSetup(boolean optimizeWrap) {
return mDependencyGraph.directMeasureSetup(optimizeWrap);
}
public boolean directMeasureWithOrientation(boolean optimizeWrap, int orientation) {
return mDependencyGraph.directMeasureWithOrientation(optimizeWrap, orientation);
}
public void defineTerminalWidgets() {
mDependencyGraph.defineTerminalWidgets(getHorizontalDimensionBehaviour(), getVerticalDimensionBehaviour());
}
////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Measure the layout
*
* @param optimizationLevel
* @param widthMode
* @param widthSize
* @param heightMode
* @param heightSize
* @param paddingX
* @param paddingY
*/
public long measure(int optimizationLevel, int widthMode, int widthSize,
int heightMode, int heightSize, int lastMeasureWidth, int lastMeasureHeight, int paddingX, int paddingY) {
mPaddingLeft = paddingX;
mPaddingTop = paddingY;
return mBasicMeasureSolver.solverMeasure(this, optimizationLevel, paddingX, paddingY, widthMode, widthSize, heightMode, heightSize,
lastMeasureWidth, lastMeasureHeight);
}
public void updateHierarchy() {
mBasicMeasureSolver.updateHierarchy(this);
}
protected BasicMeasure.Measurer mMeasurer = null;
public void setMeasurer(BasicMeasure.Measurer measurer) {
mMeasurer = measurer;
mDependencyGraph.setMeasurer(measurer);
}
public BasicMeasure.Measurer getMeasurer() {
return mMeasurer;
}
private boolean mIsRtl = false;
public Metrics mMetrics;
public void fillMetrics(Metrics metrics) {
mMetrics = metrics;
mSystem.fillMetrics(metrics);
}
protected LinearSystem mSystem = new LinearSystem();
int mPaddingLeft;
int mPaddingTop;
int mPaddingRight;
int mPaddingBottom;
public int mHorizontalChainsSize = 0;
public int mVerticalChainsSize = 0;
ChainHead[] mVerticalChainsArray = new ChainHead[4];
ChainHead[] mHorizontalChainsArray = new ChainHead[4];
public boolean mGroupsWrapOptimized = false;
public boolean mHorizontalWrapOptimized = false;
public boolean mVerticalWrapOptimized = false;
public int mWrapFixedWidth = 0;
public int mWrapFixedHeight = 0;
private int mOptimizationLevel = Optimizer.OPTIMIZATION_STANDARD;
public boolean mSkipSolver = false;
private boolean mWidthMeasuredTooSmall = false;
private boolean mHeightMeasuredTooSmall = false;
/*-----------------------------------------------------------------------*/
// Construction
/*-----------------------------------------------------------------------*/
/**
* Default constructor
*/
public ConstraintWidgetContainer() {
}
/**
* Constructor
*
* @param x x position
* @param y y position
* @param width width of the layout
* @param height height of the layout
*/
public ConstraintWidgetContainer(int x, int y, int width, int height) {
super(x, y, width, height);
}
/**
* Constructor
*
* @param width width of the layout
* @param height height of the layout
*/
public ConstraintWidgetContainer(int width, int height) {
super(width, height);
}
public ConstraintWidgetContainer(String debugName, int width, int height) {
super(width, height);
setDebugName(debugName);
}
/**
* Resolves the system directly when possible
*
* @param value optimization level
*/
public void setOptimizationLevel(int value) {
mOptimizationLevel = value;
mSystem.USE_DEPENDENCY_ORDERING = optimizeFor(Optimizer.OPTIMIZATION_DEPENDENCY_ORDERING);
}
/**
* Returns the current optimization level
*
* @return
*/
public int getOptimizationLevel() {
return mOptimizationLevel;
}
/**
* Returns true if the given feature should be optimized
*
* @param feature
* @return
*/
public boolean optimizeFor(int feature) {
return (mOptimizationLevel & feature) == feature;
}
/**
* Specify the xml type for the container
*
* @return
*/
@Override
public String getType() {
return "ConstraintLayout";
}
@Override
public void reset() {
mSystem.reset();
mPaddingLeft = 0;
mPaddingRight = 0;
mPaddingTop = 0;
mPaddingBottom = 0;
mSkipSolver = false;
super.reset();
}
/**
* Return true if the width given is too small for the content laid out
*/
public boolean isWidthMeasuredTooSmall() {
return mWidthMeasuredTooSmall;
}
/**
* Return true if the height given is too small for the content laid out
*/
public boolean isHeightMeasuredTooSmall() {
return mHeightMeasuredTooSmall;
}
int mDebugSolverPassCount = 0;
private WeakReference<ConstraintAnchor> verticalWrapMin = null;
private WeakReference<ConstraintAnchor> horizontalWrapMin = null;
private WeakReference<ConstraintAnchor> verticalWrapMax = null;
private WeakReference<ConstraintAnchor> horizontalWrapMax = null;
void addVerticalWrapMinVariable(ConstraintAnchor top) {
if (verticalWrapMin == null || verticalWrapMin.get() == null
|| top.getFinalValue() > verticalWrapMin.get().getFinalValue()) {
verticalWrapMin = new WeakReference<>(top);
}
}
public void addHorizontalWrapMinVariable(ConstraintAnchor left) {
if (horizontalWrapMin == null || horizontalWrapMin.get() == null
|| left.getFinalValue() > horizontalWrapMin.get().getFinalValue()) {
horizontalWrapMin = new WeakReference<>(left);
}
}
void addVerticalWrapMaxVariable(ConstraintAnchor bottom) {
if (verticalWrapMax == null || verticalWrapMax.get() == null
|| bottom.getFinalValue() > verticalWrapMax.get().getFinalValue()) {
verticalWrapMax = new WeakReference<>(bottom);
}
}
public void addHorizontalWrapMaxVariable(ConstraintAnchor right) {
if (horizontalWrapMax == null || horizontalWrapMax.get() == null
|| right.getFinalValue() > horizontalWrapMax.get().getFinalValue()) {
horizontalWrapMax = new WeakReference<>(right);
}
}
private void addMinWrap(ConstraintAnchor constraintAnchor, SolverVariable parentMin) {
SolverVariable variable = mSystem.createObjectVariable(constraintAnchor);
int wrapStrength = SolverVariable.STRENGTH_EQUALITY;
mSystem.addGreaterThan(variable, parentMin, 0, wrapStrength);
}
private void addMaxWrap(ConstraintAnchor constraintAnchor, SolverVariable parentMax) {
SolverVariable variable = mSystem.createObjectVariable(constraintAnchor);
int wrapStrength = SolverVariable.STRENGTH_EQUALITY;
mSystem.addGreaterThan(parentMax, variable, 0, wrapStrength);
}
HashSet<ConstraintWidget> widgetsToAdd = new HashSet<>();
/**
* Add this widget to the solver
*
* @param system the solver we want to add the widget to
*/
public boolean addChildrenToSolver(LinearSystem system) {
if (DEBUG) {
System.out.println("\n#######################################");
System.out.println("## ADD CHILDREN TO SOLVER (" + mDebugSolverPassCount + ") ##");
System.out.println("#######################################\n");
mDebugSolverPassCount++;
}
boolean optimize = optimizeFor(Optimizer.OPTIMIZATION_GRAPH);
addToSolver(system, optimize);
final int count = mChildren.size();
boolean hasBarriers = false;
for (int i = 0; i < count; i++) {
ConstraintWidget widget = mChildren.get(i);
widget.setInBarrier(HORIZONTAL, false);
widget.setInBarrier(VERTICAL, false);
if (widget instanceof Barrier) {
hasBarriers = true;
}
}
if (hasBarriers) {
for (int i = 0; i < count; i++) {
ConstraintWidget widget = mChildren.get(i);
if (widget instanceof Barrier) {
((Barrier) widget).markWidgets();
}
}
}
widgetsToAdd.clear();
for (int i = 0; i < count; i++) {
ConstraintWidget widget = mChildren.get(i);
if (widget.addFirst()) {
if (widget instanceof VirtualLayout) {
widgetsToAdd.add(widget);
} else {
widget.addToSolver(system, optimize);
}
}
}
// If we have virtual layouts, we need to add them to the solver in the correct
// order (in case they reference one another)
while (widgetsToAdd.size() > 0) {
int numLayouts = widgetsToAdd.size();
VirtualLayout layout = null;
for (ConstraintWidget widget : widgetsToAdd) {
layout = (VirtualLayout) widget;
// we'll go through the virtual layouts that references others first, to give
// them a shot at setting their constraints.
if (layout.contains(widgetsToAdd)) {
layout.addToSolver(system, optimize);
widgetsToAdd.remove(layout);
break;
}
}
if (numLayouts == widgetsToAdd.size()) {
// looks we didn't find anymore dependency, let's add everything.
for (ConstraintWidget widget : widgetsToAdd) {
widget.addToSolver(system, optimize);
}
widgetsToAdd.clear();
}
}
if (LinearSystem.USE_DEPENDENCY_ORDERING) {
HashSet<ConstraintWidget> widgetsToAdd = new HashSet<>();
for (int i = 0; i < count; i++) {
ConstraintWidget widget = mChildren.get(i);
if (!widget.addFirst()) {
widgetsToAdd.add(widget);
}
}
int orientation = VERTICAL;
if (getHorizontalDimensionBehaviour() == WRAP_CONTENT) {
orientation = HORIZONTAL;
}
addChildrenToSolverByDependency(this, system, widgetsToAdd, orientation, false);
for (ConstraintWidget widget : widgetsToAdd) {
Optimizer.checkMatchParent(this, system, widget);
widget.addToSolver(system, optimize);
}
} else {
for (int i = 0; i < count; i++) {
ConstraintWidget widget = mChildren.get(i);
if (widget instanceof ConstraintWidgetContainer) {
DimensionBehaviour horizontalBehaviour = widget.mListDimensionBehaviors[DIMENSION_HORIZONTAL];
DimensionBehaviour verticalBehaviour = widget.mListDimensionBehaviors[DIMENSION_VERTICAL];
if (horizontalBehaviour == WRAP_CONTENT) {
widget.setHorizontalDimensionBehaviour(FIXED);
}
if (verticalBehaviour == WRAP_CONTENT) {
widget.setVerticalDimensionBehaviour(FIXED);
}
widget.addToSolver(system, optimize);
if (horizontalBehaviour == WRAP_CONTENT) {
widget.setHorizontalDimensionBehaviour(horizontalBehaviour);
}
if (verticalBehaviour == WRAP_CONTENT) {
widget.setVerticalDimensionBehaviour(verticalBehaviour);
}
} else {
Optimizer.checkMatchParent(this, system, widget);
if (!(widget.addFirst())) {
widget.addToSolver(system, optimize);
}
}
}
}
if (mHorizontalChainsSize > 0) {
Chain.applyChainConstraints(this, system, null, HORIZONTAL);
}
if (mVerticalChainsSize > 0) {
Chain.applyChainConstraints(this, system, null, VERTICAL);
}
return true;
}
/**
* Update the frame of the layout and its children from the solver
*
* @param system the solver we get the values from.
*/
public boolean updateChildrenFromSolver(LinearSystem system, boolean flags[]) {
flags[Optimizer.FLAG_RECOMPUTE_BOUNDS] = false;
boolean optimize = optimizeFor(Optimizer.OPTIMIZATION_GRAPH);
updateFromSolver(system, optimize);
final int count = mChildren.size();
boolean hasOverride = false;
for (int i = 0; i < count; i++) {
ConstraintWidget widget = mChildren.get(i);
widget.updateFromSolver(system, optimize);
if (widget.hasDimensionOverride()) {
hasOverride = true;
}
}
return hasOverride;
}
@Override
public void updateFromRuns(boolean updateHorizontal, boolean updateVertical) {
super.updateFromRuns(updateHorizontal, updateVertical);
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
ConstraintWidget widget = mChildren.get(i);
widget.updateFromRuns(updateHorizontal, updateVertical);
}
}
/**
* Set the padding on this container. It will apply to the position of the children.
*
* @param left left padding
* @param top top padding
* @param right right padding
* @param bottom bottom padding
*/
public void setPadding(int left, int top, int right, int bottom) {
mPaddingLeft = left;
mPaddingTop = top;
mPaddingRight = right;
mPaddingBottom = bottom;
}
/**
* Set the rtl status. This has implications for Chains.
*
* @param isRtl true if we are in RTL.
*/
public void setRtl(boolean isRtl) {
mIsRtl = isRtl;
}
/**
* Returns the rtl status.
*
* @return true if in RTL, false otherwise.
*/
public boolean isRtl() {
return mIsRtl;
}
/*-----------------------------------------------------------------------*/
// Overloaded methods from ConstraintWidget
/*-----------------------------------------------------------------------*/
public BasicMeasure.Measure mMeasure = new BasicMeasure.Measure();
public static boolean measure(int level, ConstraintWidget widget, BasicMeasure.Measurer measurer, BasicMeasure.Measure measure, int measureStrategy) {
if (DEBUG) {
System.out.println(Direct.ls(level) + "(M) call to measure " + widget.getDebugName());
}
if (measurer == null) {
return false;
}
if (widget.getVisibility() == GONE
|| widget instanceof Guideline
|| widget instanceof Barrier) {
if (DEBUG) {
System.out.println(Direct.ls(level) + "(M) no measure needed for " + widget.getDebugName());
}
measure.measuredWidth = 0;
measure.measuredHeight = 0;
return false;
}
measure.horizontalBehavior = widget.getHorizontalDimensionBehaviour();
measure.verticalBehavior = widget.getVerticalDimensionBehaviour();
measure.horizontalDimension = widget.getWidth();
measure.verticalDimension = widget.getHeight();
measure.measuredNeedsSolverPass = false;
measure.measureStrategy = measureStrategy;
boolean horizontalMatchConstraints = (measure.horizontalBehavior == DimensionBehaviour.MATCH_CONSTRAINT);
boolean verticalMatchConstraints = (measure.verticalBehavior == DimensionBehaviour.MATCH_CONSTRAINT);
boolean horizontalUseRatio = horizontalMatchConstraints && widget.mDimensionRatio > 0;
boolean verticalUseRatio = verticalMatchConstraints && widget.mDimensionRatio > 0;
if (horizontalMatchConstraints && widget.hasDanglingDimension(HORIZONTAL)
&& widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD
&& !horizontalUseRatio) {
horizontalMatchConstraints = false;
measure.horizontalBehavior = WRAP_CONTENT;
if (verticalMatchConstraints && widget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD) {
// if match x match, size would be zero.
measure.horizontalBehavior = FIXED;
}
}
if (verticalMatchConstraints && widget.hasDanglingDimension(VERTICAL)
&& widget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD
&& !verticalUseRatio) {
verticalMatchConstraints = false;
measure.verticalBehavior = WRAP_CONTENT;
if (horizontalMatchConstraints && widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD) {
// if match x match, size would be zero.
measure.verticalBehavior = FIXED;
}
}
if (widget.isResolvedHorizontally()) {
horizontalMatchConstraints = false;
measure.horizontalBehavior = FIXED;
}
if (widget.isResolvedVertically()) {
verticalMatchConstraints = false;
measure.verticalBehavior = FIXED;
}
if (horizontalUseRatio) {
if (widget.mResolvedMatchConstraintDefault[HORIZONTAL] == ConstraintWidget.MATCH_CONSTRAINT_RATIO_RESOLVED) {
measure.horizontalBehavior = FIXED;
} else if (!verticalMatchConstraints) {
// let's measure here
int measuredHeight;
if (measure.verticalBehavior == FIXED) {
measuredHeight = measure.verticalDimension;
} else {
measure.horizontalBehavior = WRAP_CONTENT;
measurer.measure(widget, measure);
measuredHeight = measure.measuredHeight;
}
measure.horizontalBehavior = FIXED;
// regardless of which side we are using for the ratio, getDimensionRatio() already
// made sure that it's expressed in WxH format, so we can simply go and multiply
measure.horizontalDimension = (int) (widget.getDimensionRatio() * measuredHeight);
if (DEBUG) {
System.out.println("(M) Measured once for ratio on horizontal side...");
}
}
}
if (verticalUseRatio) {
if (widget.mResolvedMatchConstraintDefault[VERTICAL] == ConstraintWidget.MATCH_CONSTRAINT_RATIO_RESOLVED) {
measure.verticalBehavior = FIXED;
} else if (!horizontalMatchConstraints) {
// let's measure here
int measuredWidth;
if (measure.horizontalBehavior == FIXED) {
measuredWidth = measure.horizontalDimension;
} else {
measure.verticalBehavior = WRAP_CONTENT;
measurer.measure(widget, measure);
measuredWidth = measure.measuredWidth;
}
measure.verticalBehavior = FIXED;
if (widget.getDimensionRatioSide() == -1) {
// regardless of which side we are using for the ratio, getDimensionRatio() already
// made sure that it's expressed in WxH format, so we can simply go and divide
measure.verticalDimension = (int) (measuredWidth / widget.getDimensionRatio());
} else {
// getDimensionRatio() already got reverted, so we can simply multiply
measure.verticalDimension = (int) (widget.getDimensionRatio() * measuredWidth);
}
if (DEBUG) {
System.out.println("(M) Measured once for ratio on vertical side...");
}
}
}
measurer.measure(widget, measure);
widget.setWidth(measure.measuredWidth);
widget.setHeight(measure.measuredHeight);
widget.setHasBaseline(measure.measuredHasBaseline);
widget.setBaselineDistance(measure.measuredBaseline);
measure.measureStrategy = BasicMeasure.Measure.SELF_DIMENSIONS;
if (DEBUG) {
System.out.println("(M) Measured " + widget.getDebugName() + " with : "
+ widget.getHorizontalDimensionBehaviour() + " x " + widget.getVerticalDimensionBehaviour()
+ " => " + widget.getWidth() + " x " + widget.getHeight());
}
return measure.measuredNeedsSolverPass;
}
static int myCounter = 0;
/**
* Layout the tree of widgets
*/
@Override
public void layout() {
if (DEBUG) {
System.out.println("\n#####################################");
System.out.println("## CL LAYOUT PASS ##");
System.out.println("#####################################\n");
mDebugSolverPassCount = 0;
}
mX = 0;
mY = 0;
mWidthMeasuredTooSmall = false;
mHeightMeasuredTooSmall = false;
final int count = mChildren.size();
int preW = Math.max(0, getWidth());
int preH = Math.max(0, getHeight());
DimensionBehaviour originalVerticalDimensionBehaviour = mListDimensionBehaviors[DIMENSION_VERTICAL];
DimensionBehaviour originalHorizontalDimensionBehaviour = mListDimensionBehaviors[DIMENSION_HORIZONTAL];
if (DEBUG_LAYOUT) {
System.out.println("layout with preW: " + preW + " (" + mListDimensionBehaviors[DIMENSION_HORIZONTAL]
+ ") preH: " + preH + " (" + mListDimensionBehaviors[DIMENSION_VERTICAL] + ")");
}
if (mMetrics != null) {
mMetrics.layouts++;
}
boolean wrap_override = false;
if (FULL_DEBUG) {
System.out.println("OPTIMIZATION LEVEL " + mOptimizationLevel);
}
// Only try the direct optimization in the first layout pass
if (pass == 0 && Optimizer.enabled(mOptimizationLevel, Optimizer.OPTIMIZATION_DIRECT)) {
if (FULL_DEBUG) {
System.out.println("Direct pass " + myCounter++);
}
Direct.solvingPass(this, getMeasurer());
if (FULL_DEBUG) {
System.out.println("Direct pass done.");
}
for (int i = 0; i < count; i++) {
ConstraintWidget child = mChildren.get(i);
if (FULL_DEBUG) {
if (child.isInHorizontalChain()) {
System.out.print("H");
} else {
System.out.print(" ");
}
if (child.isInVerticalChain()) {
System.out.print("V");
} else {
System.out.print(" ");
}
if (child.isResolvedHorizontally() && child.isResolvedVertically()) {
System.out.print("*");
} else {
System.out.print(" ");
}
System.out.println("[" + i + "] child " + child.getDebugName()
+ " H: " + child.isResolvedHorizontally()
+ " V: " + child.isResolvedVertically());
}
if (child.isMeasureRequested()
&& !(child instanceof Guideline)
&& !(child instanceof Barrier)
&& !(child instanceof VirtualLayout)
&& !(child.isInVirtualLayout())) {
DimensionBehaviour widthBehavior = child.getDimensionBehaviour(HORIZONTAL);
DimensionBehaviour heightBehavior = child.getDimensionBehaviour(VERTICAL);
boolean skip = widthBehavior == DimensionBehaviour.MATCH_CONSTRAINT
&& child.mMatchConstraintDefaultWidth != MATCH_CONSTRAINT_WRAP
&& heightBehavior == DimensionBehaviour.MATCH_CONSTRAINT
&& child.mMatchConstraintDefaultHeight != MATCH_CONSTRAINT_WRAP;
if (!skip) {
BasicMeasure.Measure measure = new BasicMeasure.Measure();
ConstraintWidgetContainer.measure(0, child, mMeasurer, measure, BasicMeasure.Measure.SELF_DIMENSIONS);
}
}
}
// let's measure children
if (FULL_DEBUG) {
System.out.println("Direct pass all done.");
}
} else {
if (FULL_DEBUG) {
System.out.println("No DIRECT PASS");
}
}
if (count > 2 && (originalHorizontalDimensionBehaviour == WRAP_CONTENT
|| originalVerticalDimensionBehaviour == WRAP_CONTENT)
&& (Optimizer.enabled(mOptimizationLevel, Optimizer.OPTIMIZATION_GROUPING))) {
if (Grouping.simpleSolvingPass(this, getMeasurer())) {
if (originalHorizontalDimensionBehaviour == WRAP_CONTENT) {
if (preW < getWidth() && preW > 0) {
if (DEBUG_LAYOUT) {
System.out.println("Override width " + getWidth() + " to " + preH);
}
setWidth(preW);
mWidthMeasuredTooSmall = true;
} else {
preW = getWidth();
}
}
if (originalVerticalDimensionBehaviour == WRAP_CONTENT) {
if (preH < getHeight() && preH > 0) {
if (DEBUG_LAYOUT) {
System.out.println("Override height " + getHeight() + " to " + preH);
}
setHeight(preH);
mHeightMeasuredTooSmall = true;
} else {
preH = getHeight();
}
}
wrap_override = true;
if (DEBUG_LAYOUT) {
System.out.println("layout post opt, preW: " + preW + " (" + mListDimensionBehaviors[DIMENSION_HORIZONTAL]
+ ") preH: " + preH + " (" + mListDimensionBehaviors[DIMENSION_VERTICAL] + "), new size "
+ getWidth() + " x " + getHeight());
}
}
}
boolean useGraphOptimizer = optimizeFor(Optimizer.OPTIMIZATION_GRAPH) || optimizeFor(Optimizer.OPTIMIZATION_GRAPH_WRAP);
mSystem.graphOptimizer = false;
mSystem.newgraphOptimizer = false;
if (mOptimizationLevel != Optimizer.OPTIMIZATION_NONE
&& useGraphOptimizer) {
mSystem.newgraphOptimizer = true;
}
int countSolve = 0;
final List<ConstraintWidget> allChildren = mChildren;
boolean hasWrapContent = getHorizontalDimensionBehaviour() == WRAP_CONTENT || getVerticalDimensionBehaviour() == WRAP_CONTENT;
// Reset the chains before iterating on our children
resetChains();
countSolve = 0;
// Before we solve our system, we should call layout() on any
// of our children that is a container.
for (int i = 0; i < count; i++) {
ConstraintWidget widget = mChildren.get(i);
if (widget instanceof WidgetContainer) {
((WidgetContainer) widget).layout();
}
}
boolean optimize = optimizeFor(Optimizer.OPTIMIZATION_GRAPH);
// Now let's solve our system as usual
boolean needsSolving = true;
while (needsSolving) {
countSolve++;
try {
mSystem.reset();
resetChains();
if (DEBUG) {
String debugName = getDebugName();
if (debugName == null) {
debugName = "root";
}
setDebugSolverName(mSystem, debugName);
for (int i = 0; i < count; i++) {
ConstraintWidget widget = mChildren.get(i);
if (widget.getDebugName() != null) {
widget.setDebugSolverName(mSystem, widget.getDebugName());
}
}
} else {
createObjectVariables(mSystem);
for (int i = 0; i < count; i++) {
ConstraintWidget widget = mChildren.get(i);
widget.createObjectVariables(mSystem);
}
}
needsSolving = addChildrenToSolver(mSystem);
if (verticalWrapMin != null && verticalWrapMin.get() != null) {
addMinWrap(verticalWrapMin.get(), mSystem.createObjectVariable(mTop));
verticalWrapMin = null;
}
if (verticalWrapMax != null && verticalWrapMax.get() != null) {
addMaxWrap(verticalWrapMax.get(), mSystem.createObjectVariable(mBottom));
verticalWrapMax = null;
}
if (horizontalWrapMin != null && horizontalWrapMin.get() != null) {
addMinWrap(horizontalWrapMin.get(), mSystem.createObjectVariable(mLeft));
horizontalWrapMin = null;
}
if (horizontalWrapMax != null && horizontalWrapMax.get() != null) {
addMaxWrap(horizontalWrapMax.get(), mSystem.createObjectVariable(mRight));
horizontalWrapMax = null;
}
if (needsSolving) {
mSystem.minimize();
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("EXCEPTION : " + e);
}
if (needsSolving) {
needsSolving = updateChildrenFromSolver(mSystem, Optimizer.flags);
} else {
updateFromSolver(mSystem, optimize);
for (int i = 0; i < count; i++) {
ConstraintWidget widget = mChildren.get(i);
widget.updateFromSolver(mSystem, optimize);
}
needsSolving = false;
}
if (hasWrapContent && countSolve < MAX_ITERATIONS
&& Optimizer.flags[Optimizer.FLAG_RECOMPUTE_BOUNDS]) {
// let's get the new bounds
int maxX = 0;
int maxY = 0;
for (int i = 0; i < count; i++) {
ConstraintWidget widget = mChildren.get(i);
maxX = Math.max(maxX, widget.mX + widget.getWidth());
maxY = Math.max(maxY, widget.mY + widget.getHeight());
}
maxX = Math.max(mMinWidth, maxX);
maxY = Math.max(mMinHeight, maxY);
if (originalHorizontalDimensionBehaviour == WRAP_CONTENT) {
if (getWidth() < maxX) {
if (DEBUG_LAYOUT) {
System.out.println(
"layout override width from " + getWidth() + " vs " + maxX);
}
setWidth(maxX);
mListDimensionBehaviors[DIMENSION_HORIZONTAL] = WRAP_CONTENT; // force using the solver
wrap_override = true;
needsSolving = true;
}
}
if (originalVerticalDimensionBehaviour == WRAP_CONTENT) {
if (getHeight() < maxY) {
if (DEBUG_LAYOUT) {
System.out.println(
"layout override height from " + getHeight() + " vs " + maxY);
}
setHeight(maxY);
mListDimensionBehaviors[DIMENSION_VERTICAL] = WRAP_CONTENT; // force using the solver
wrap_override = true;
needsSolving = true;
}
}
}
if (true) {
int width = Math.max(mMinWidth, getWidth());
if (width > getWidth()) {
if (DEBUG_LAYOUT) {
System.out.println(
"layout override 2, width from " + getWidth() + " vs " + width);
}
setWidth(width);
mListDimensionBehaviors[DIMENSION_HORIZONTAL] = FIXED;
wrap_override = true;
needsSolving = true;
}
int height = Math.max(mMinHeight, getHeight());
if (height > getHeight()) {
if (DEBUG_LAYOUT) {
System.out.println(
"layout override 2, height from " + getHeight() + " vs " + height);
}
setHeight(height);
mListDimensionBehaviors[DIMENSION_VERTICAL] = FIXED;
wrap_override = true;
needsSolving = true;
}
if (!wrap_override) {
if (mListDimensionBehaviors[DIMENSION_HORIZONTAL] == WRAP_CONTENT
&& preW > 0) {
if (getWidth() > preW) {
if (DEBUG_LAYOUT) {
System.out.println(
"layout override 3, width from " + getWidth() + " vs "
+ preW);
}
mWidthMeasuredTooSmall = true;
wrap_override = true;
mListDimensionBehaviors[DIMENSION_HORIZONTAL] = FIXED;
setWidth(preW);
needsSolving = true;
}
}
if (mListDimensionBehaviors[DIMENSION_VERTICAL] == WRAP_CONTENT
&& preH > 0) {
if (getHeight() > preH) {
if (DEBUG_LAYOUT) {
System.out.println(
"layout override 3, height from " + getHeight() + " vs "
+ preH);
}
mHeightMeasuredTooSmall = true;
wrap_override = true;
mListDimensionBehaviors[DIMENSION_VERTICAL] = FIXED;
setHeight(preH);
needsSolving = true;
}
}
}
if (countSolve > MAX_ITERATIONS) {
needsSolving = false;
}
}
}
if (DEBUG_LAYOUT) {
System.out.println(
"Solved system in " + countSolve + " iterations (" + getWidth() + " x "
+ getHeight() + ")");
}
mChildren = (ArrayList<ConstraintWidget>) allChildren;
if (wrap_override) {
mListDimensionBehaviors[DIMENSION_HORIZONTAL] = originalHorizontalDimensionBehaviour;
mListDimensionBehaviors[DIMENSION_VERTICAL] = originalVerticalDimensionBehaviour;
}
resetSolverVariables(mSystem.getCache());
}
/**
* Indicates if the container knows how to layout its content on its own
*
* @return true if the container does the layout, false otherwise
*/
public boolean handlesInternalConstraints() {
return false;
}
/*-----------------------------------------------------------------------*/
// Guidelines
/*-----------------------------------------------------------------------*/
/**
* Accessor to the vertical guidelines contained in the table.
*
* @return array of guidelines
*/
public ArrayList<Guideline> getVerticalGuidelines() {
ArrayList<Guideline> guidelines = new ArrayList<>();
for (int i = 0, mChildrenSize = mChildren.size(); i < mChildrenSize; i++) {
final ConstraintWidget widget = mChildren.get(i);
if (widget instanceof Guideline) {
Guideline guideline = (Guideline) widget;
if (guideline.getOrientation() == Guideline.VERTICAL) {
guidelines.add(guideline);
}
}
}
return guidelines;
}
/**
* Accessor to the horizontal guidelines contained in the table.
*
* @return array of guidelines
*/
public ArrayList<Guideline> getHorizontalGuidelines() {
ArrayList<Guideline> guidelines = new ArrayList<>();
for (int i = 0, mChildrenSize = mChildren.size(); i < mChildrenSize; i++) {
final ConstraintWidget widget = mChildren.get(i);
if (widget instanceof Guideline) {
Guideline guideline = (Guideline) widget;
if (guideline.getOrientation() == Guideline.HORIZONTAL) {
guidelines.add(guideline);
}
}
}
return guidelines;
}
public LinearSystem getSystem() {
return mSystem;
}
/*-----------------------------------------------------------------------*/
// Chains
/*-----------------------------------------------------------------------*/
/**
* Reset the chains array. Need to be called before layout.
*/
private void resetChains() {
mHorizontalChainsSize = 0;
mVerticalChainsSize = 0;
}
/**
* Add the chain which constraintWidget is part of. Called by ConstraintWidget::addToSolver()
*
* @param constraintWidget
* @param type HORIZONTAL or VERTICAL chain
*/
void addChain(ConstraintWidget constraintWidget, int type) {
ConstraintWidget widget = constraintWidget;
if (type == HORIZONTAL) {
addHorizontalChain(widget);
} else if (type == VERTICAL) {
addVerticalChain(widget);
}
}
/**
* Add a widget to the list of horizontal chains. The widget is the left-most widget
* of the chain which doesn't have a left dual connection.
*
* @param widget widget starting the chain
*/
private void addHorizontalChain(ConstraintWidget widget) {
if (mHorizontalChainsSize + 1 >= mHorizontalChainsArray.length) {
mHorizontalChainsArray = Arrays
.copyOf(mHorizontalChainsArray, mHorizontalChainsArray.length * 2);
}
mHorizontalChainsArray[mHorizontalChainsSize] = new ChainHead(widget, HORIZONTAL, isRtl());
mHorizontalChainsSize++;
}
/**
* Add a widget to the list of vertical chains. The widget is the top-most widget
* of the chain which doesn't have a top dual connection.
*
* @param widget widget starting the chain
*/
private void addVerticalChain(ConstraintWidget widget) {
if (mVerticalChainsSize + 1 >= mVerticalChainsArray.length) {
mVerticalChainsArray = Arrays
.copyOf(mVerticalChainsArray, mVerticalChainsArray.length * 2);
}
mVerticalChainsArray[mVerticalChainsSize] = new ChainHead(widget, VERTICAL, isRtl());
mVerticalChainsSize++;
}
/**
* Keep track of the # of passes
* @param pass
*/
public void setPass(int pass) {
this.pass = pass;
}
}