blob: 16c891086d64766bbc74f49da466d28906632396 [file] [log] [blame]
/*
* Copyright (C) 2017 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 android.support.constraint.solver.widgets;
import android.support.constraint.solver.LinearSystem;
import android.support.constraint.solver.SolverVariable;
import java.util.ArrayList;
import java.util.Arrays;
/**
* A Barrier takes multiple widgets
*/
public class Barrier extends Helper {
public static final int LEFT = 0;
public static final int RIGHT = 1;
public static final int TOP = 2;
public static final int BOTTOM = 3;
private int mBarrierType = LEFT;
private ArrayList<ResolutionAnchor> mNodes = new ArrayList<>(4);
private boolean mAllowsGoneWidget = true;
@Override
public boolean allowedInBarrier() {
return true;
}
public void setBarrierType(int barrierType) {
mBarrierType = barrierType;
}
public void setAllowsGoneWidget(boolean allowsGoneWidget) { mAllowsGoneWidget = allowsGoneWidget; }
public boolean allowsGoneWidget() { return mAllowsGoneWidget; }
@Override
public void resetResolutionNodes() {
super.resetResolutionNodes();
mNodes.clear();
}
/**
* Graph analysis
* @param optimizationLevel
*/
@Override
public void analyze(int optimizationLevel) {
if (mParent == null) {
return;
}
if (!((ConstraintWidgetContainer) mParent).optimizeFor(Optimizer.OPTIMIZATION_BARRIER)) {
return;
}
ResolutionAnchor node;
switch (mBarrierType) {
case LEFT:
node = mLeft.getResolutionNode();
break;
case RIGHT:
node = mRight.getResolutionNode();
break;
case TOP:
node = mTop.getResolutionNode();
break;
case BOTTOM:
node = mBottom.getResolutionNode();
break;
default:
return;
}
node.setType(ResolutionAnchor.BARRIER_CONNECTION);
if (mBarrierType == LEFT || mBarrierType == RIGHT) {
mTop.getResolutionNode().resolve(null, 0);
mBottom.getResolutionNode().resolve(null, 0);
} else {
mLeft.getResolutionNode().resolve(null, 0);
mRight.getResolutionNode().resolve(null, 0);
}
mNodes.clear();
for (int i = 0; i < mWidgetsCount; i++) {
ConstraintWidget widget = mWidgets[i];
if (!mAllowsGoneWidget && !widget.allowedInBarrier()) {
continue;
}
ResolutionAnchor depends = null;
switch (mBarrierType) {
case LEFT:
depends = widget.mLeft.getResolutionNode();
break;
case RIGHT:
depends = widget.mRight.getResolutionNode();
break;
case TOP:
depends = widget.mTop.getResolutionNode();
break;
case BOTTOM:
depends = widget.mBottom.getResolutionNode();
break;
}
if (depends != null) {
mNodes.add(depends);
depends.addDependent(node);
}
}
}
/**
* Try resolving the graph analysis
*/
@Override
public void resolve() {
ResolutionAnchor node = null;
float value = 0;
switch (mBarrierType) {
case LEFT: {
node = mLeft.getResolutionNode();
value = Float.MAX_VALUE;
} break;
case RIGHT: {
node = mRight.getResolutionNode();
} break;
case TOP: {
node = mTop.getResolutionNode();
value = Float.MAX_VALUE;
} break;
case BOTTOM: {
node = mBottom.getResolutionNode();
} break;
default:
return;
}
final int count = mNodes.size();
ResolutionAnchor resolvedTarget = null;
for (int i = 0; i < count; i++) {
ResolutionAnchor n = mNodes.get(i);
if (n.state != ResolutionAnchor.RESOLVED) {
return;
}
if (mBarrierType == LEFT || mBarrierType == TOP) {
if (n.resolvedOffset < value) {
value = n.resolvedOffset;
resolvedTarget = n.resolvedTarget;
}
} else {
if (n.resolvedOffset > value) {
value = n.resolvedOffset;
resolvedTarget = n.resolvedTarget;
}
}
}
if (LinearSystem.getMetrics() != null) {
LinearSystem.getMetrics().barrierConnectionResolved++;
}
if (ConstraintWidgetContainer.DEBUG_GRAPH) {
System.out.println(" * barrier resolved to " + resolvedTarget + " : " + value);
}
node.resolvedTarget = resolvedTarget;
node.resolvedOffset = value;
node.didResolve();
switch (mBarrierType) {
case LEFT: {
mRight.getResolutionNode().resolve(resolvedTarget, value);
} break;
case RIGHT: {
mLeft.getResolutionNode().resolve(resolvedTarget, value);
} break;
case TOP: {
mBottom.getResolutionNode().resolve(resolvedTarget, value);
} break;
case BOTTOM: {
mTop.getResolutionNode().resolve(resolvedTarget, value);
} break;
default:
return;
}
}
/**
* Add this widget to the solver
*
* @param system the solver we want to add the widget to
*/
@Override
public void addToSolver(LinearSystem system) {
ConstraintAnchor position;
mListAnchors[LEFT] = mLeft;
mListAnchors[TOP] = mTop;
mListAnchors[RIGHT] = mRight;
mListAnchors[BOTTOM] = mBottom;
for (int i = 0; i < mListAnchors.length; i++) {
mListAnchors[i].mSolverVariable = system.createObjectVariable(mListAnchors[i]);
}
if (mBarrierType >= 0 && mBarrierType < 4) {
position = mListAnchors[mBarrierType];
} else {
return;
}
// We have to handle the case where some of the elements referenced in the barrier are set as
// match_constraint; we have to take it in account to set the strength of the barrier.
boolean hasMatchConstraintWidgets = false;
for (int i = 0; i < mWidgetsCount; i++) {
ConstraintWidget widget = mWidgets[i];
if (!mAllowsGoneWidget && !widget.allowedInBarrier()) {
continue;
}
if ((mBarrierType == LEFT || mBarrierType == RIGHT)
&& widget.getHorizontalDimensionBehaviour() == DimensionBehaviour.MATCH_CONSTRAINT) {
hasMatchConstraintWidgets = true;
break;
} else if ((mBarrierType == TOP || mBarrierType == BOTTOM)
&& widget.getVerticalDimensionBehaviour() == DimensionBehaviour.MATCH_CONSTRAINT) {
hasMatchConstraintWidgets = true;
break;
}
}
if ((mBarrierType == LEFT || mBarrierType == RIGHT)) {
if (getParent().getHorizontalDimensionBehaviour() == DimensionBehaviour.WRAP_CONTENT) {
hasMatchConstraintWidgets = false;
}
} else {
if (getParent().getVerticalDimensionBehaviour() == DimensionBehaviour.WRAP_CONTENT) {
hasMatchConstraintWidgets = false;
}
}
for (int i = 0; i < mWidgetsCount; i++) {
ConstraintWidget widget = mWidgets[i];
if (!mAllowsGoneWidget && !widget.allowedInBarrier()) {
continue;
}
SolverVariable target = system.createObjectVariable(widget.mListAnchors[mBarrierType]);
widget.mListAnchors[mBarrierType].mSolverVariable = target;
if (mBarrierType == LEFT || mBarrierType == TOP) {
system.addLowerBarrier(position.mSolverVariable, target, hasMatchConstraintWidgets);
} else {
system.addGreaterBarrier(position.mSolverVariable, target, hasMatchConstraintWidgets);
}
}
if (mBarrierType == LEFT) {
system.addEquality(mRight.mSolverVariable, mLeft.mSolverVariable, 0, SolverVariable.STRENGTH_FIXED);
if (!hasMatchConstraintWidgets) {
system.addEquality(mLeft.mSolverVariable, mParent.mRight.mSolverVariable, 0, SolverVariable.STRENGTH_EQUALITY);
}
} else if (mBarrierType == RIGHT) {
system.addEquality(mLeft.mSolverVariable, mRight.mSolverVariable, 0, SolverVariable.STRENGTH_FIXED);
if (!hasMatchConstraintWidgets) {
system.addEquality(mLeft.mSolverVariable, mParent.mLeft.mSolverVariable, 0, SolverVariable.STRENGTH_EQUALITY);
}
} else if (mBarrierType == TOP) {
system.addEquality(mBottom.mSolverVariable, mTop.mSolverVariable, 0, SolverVariable.STRENGTH_FIXED);
if (!hasMatchConstraintWidgets) {
system.addEquality(mTop.mSolverVariable, mParent.mBottom.mSolverVariable, 0, SolverVariable.STRENGTH_EQUALITY);
}
} else if (mBarrierType == BOTTOM) {
system.addEquality(mTop.mSolverVariable, mBottom.mSolverVariable, 0, SolverVariable.STRENGTH_FIXED);
if (!hasMatchConstraintWidgets) {
system.addEquality(mTop.mSolverVariable, mParent.mTop.mSolverVariable, 0, SolverVariable.STRENGTH_EQUALITY);
}
}
}
}