blob: e73dea0dd053f8c0e22dfb575a757f32ca692511 [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.ArrayRow;
import android.support.constraint.solver.LinearSystem;
import android.support.constraint.solver.SolverVariable;
import java.util.ArrayList;
import static android.support.constraint.solver.widgets.ConstraintWidget.*;
/**
* Chain management and constraints creation
*/
class Chain {
private static final boolean DEBUG = false;
/**
* Apply specific rules for dealing with chains of widgets.
* Chains are defined as a list of widget linked together with bi-directional connections
*
* @param constraintWidgetContainer root container
* @param system the linear system we add the equations to
* @param orientation HORIZONTAL or VERTICAL
*/
static void applyChainConstraints(ConstraintWidgetContainer constraintWidgetContainer, LinearSystem system, int orientation) {
// what to do:
// Don't skip things. Either the element is GONE or not.
int offset = 0;
int chainsSize = 0;
ChainHead[] chainsArray = null;
if (orientation == ConstraintWidget.HORIZONTAL) {
offset = 0;
chainsSize = constraintWidgetContainer.mHorizontalChainsSize;
chainsArray = constraintWidgetContainer.mHorizontalChainsArray;
} else {
offset = 2;
chainsSize = constraintWidgetContainer.mVerticalChainsSize;
chainsArray = constraintWidgetContainer.mVerticalChainsArray;
}
for (int i = 0; i < chainsSize; i++) {
ChainHead first = chainsArray[i];
// we have to make sure we define the ChainHead here, otherwise the values we use may not
// be correctly initialized (as we initialize them in the ConstraintWidget.addToSolver())
first.define();
if (constraintWidgetContainer.optimizeFor(Optimizer.OPTIMIZATION_CHAIN)) {
if (!Optimizer.applyChainOptimized(constraintWidgetContainer, system, orientation, offset, first)) {
applyChainConstraints(constraintWidgetContainer, system, orientation, offset, first);
}
} else {
applyChainConstraints(constraintWidgetContainer, system, orientation, offset, first);
}
}
}
/**
* Apply specific rules for dealing with chains of widgets.
* Chains are defined as a list of widget linked together with bi-directional connections
*
* @param container the root container
* @param system the linear system we add the equations to
* @param orientation HORIZONTAL or VERTICAL
* @param offset 0 or 2 to accomodate for HORIZONTAL / VERTICAL
* @param chainHead a chain represented by its main elements
*/
static void applyChainConstraints(ConstraintWidgetContainer container, LinearSystem system,
int orientation, int offset, ChainHead chainHead) {
ConstraintWidget first = chainHead.mFirst;
ConstraintWidget last = chainHead.mLast;
ConstraintWidget firstVisibleWidget = chainHead.mFirstVisibleWidget;
ConstraintWidget lastVisibleWidget = chainHead.mLastVisibleWidget;
ConstraintWidget head = chainHead.mHead;
ConstraintWidget widget = first;
ConstraintWidget next = null;
boolean done = false;
float totalWeights = chainHead.mTotalWeight;
ConstraintWidget firstMatchConstraintsWidget = chainHead.mFirstMatchConstraintWidget;
ConstraintWidget previousMatchConstraintsWidget = chainHead.mLastMatchConstraintWidget;
boolean isWrapContent = container.mListDimensionBehaviors[orientation] == ConstraintWidget.DimensionBehaviour.WRAP_CONTENT;
boolean isChainSpread = false;
boolean isChainSpreadInside = false;
boolean isChainPacked = false;
if (orientation == ConstraintWidget.HORIZONTAL) {
isChainSpread = head.mHorizontalChainStyle == ConstraintWidget.CHAIN_SPREAD;
isChainSpreadInside = head.mHorizontalChainStyle == ConstraintWidget.CHAIN_SPREAD_INSIDE;
isChainPacked = head.mHorizontalChainStyle == ConstraintWidget.CHAIN_PACKED;
} else {
isChainSpread = head.mVerticalChainStyle == ConstraintWidget.CHAIN_SPREAD;
isChainSpreadInside = head.mVerticalChainStyle == ConstraintWidget.CHAIN_SPREAD_INSIDE;
isChainPacked = head.mVerticalChainStyle == ConstraintWidget.CHAIN_PACKED;
}
// The first traversal will:
// - set up some basic ordering constraints
// - build a linked list of matched constraints widgets
while (!done) {
ConstraintAnchor begin = widget.mListAnchors[offset];
int strength = SolverVariable.STRENGTH_HIGHEST;
if (isWrapContent || isChainPacked) {
strength = SolverVariable.STRENGTH_LOW;
}
int margin = begin.getMargin();
if (begin.mTarget != null && widget != first) {
margin += begin.mTarget.getMargin();
}
if (isChainPacked && widget != first && widget != firstVisibleWidget) {
strength = SolverVariable.STRENGTH_FIXED;
} else if (isChainSpread && isWrapContent) {
// on chain spread, keep the default strength connecting to endpoints to highest
// this makes it on par with ratio strength.
strength = SolverVariable.STRENGTH_HIGHEST;
}
if (begin.mTarget != null) {
if (widget == firstVisibleWidget) {
system.addGreaterThan(begin.mSolverVariable, begin.mTarget.mSolverVariable,
margin, SolverVariable.STRENGTH_EQUALITY);
} else {
system.addGreaterThan(begin.mSolverVariable, begin.mTarget.mSolverVariable,
margin, SolverVariable.STRENGTH_FIXED);
}
system.addEquality(begin.mSolverVariable, begin.mTarget.mSolverVariable, margin,
strength);
}
if (isWrapContent) {
if(widget.getVisibility() != ConstraintWidget.GONE
&& widget.mListDimensionBehaviors[orientation] == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT){
system.addGreaterThan(widget.mListAnchors[offset + 1].mSolverVariable,
widget.mListAnchors[offset].mSolverVariable, 0,
SolverVariable.STRENGTH_EQUALITY);
}
system.addGreaterThan(widget.mListAnchors[offset].mSolverVariable,
container.mListAnchors[offset].mSolverVariable,
0, SolverVariable.STRENGTH_FIXED);
}
// go to the next widget
ConstraintAnchor nextAnchor = widget.mListAnchors[offset + 1].mTarget;
if (nextAnchor != null) {
next = nextAnchor.mOwner;
if (next.mListAnchors[offset].mTarget == null || next.mListAnchors[offset].mTarget.mOwner != widget) {
next = null;
}
} else {
next = null;
}
if (next != null) {
widget = next;
} else {
done = true;
}
}
// Make sure we have constraints for the last anchors / targets
if (lastVisibleWidget != null && last.mListAnchors[offset + 1].mTarget != null) {
ConstraintAnchor end = lastVisibleWidget.mListAnchors[offset + 1];
system.addLowerThan(end.mSolverVariable,
last.mListAnchors[offset + 1].mTarget.mSolverVariable, -end.getMargin(),
SolverVariable.STRENGTH_EQUALITY);
}
// ... and make sure the root end is constrained in wrap content.
if (isWrapContent) {
system.addGreaterThan(container.mListAnchors[offset + 1].mSolverVariable,
last.mListAnchors[offset + 1].mSolverVariable,
last.mListAnchors[offset + 1].getMargin(), SolverVariable.STRENGTH_FIXED);
}
// Now, let's apply the centering / spreading for matched constraints widgets
ArrayList<ConstraintWidget> listMatchConstraints = chainHead.mWeightedMatchConstraintsWidgets;
if (listMatchConstraints != null) {
final int count = listMatchConstraints.size();
if (count > 1) {
ConstraintWidget lastMatch = null;
float lastWeight = 0;
if (chainHead.mHasUndefinedWeights && !chainHead.mHasComplexMatchWeights) {
totalWeights = chainHead.mWidgetsMatchCount;
}
for (int i = 0; i < count; i++) {
ConstraintWidget match = listMatchConstraints.get(i);
float currentWeight = match.mWeight[orientation];
if (currentWeight < 0) {
if (chainHead.mHasComplexMatchWeights) {
system.addEquality(match.mListAnchors[offset + 1].mSolverVariable,
match.mListAnchors[offset].mSolverVariable, 0, SolverVariable.STRENGTH_HIGHEST);
continue;
}
currentWeight = 1;
}
if (currentWeight == 0) {
system.addEquality(match.mListAnchors[offset + 1].mSolverVariable,
match.mListAnchors[offset].mSolverVariable, 0, SolverVariable.STRENGTH_FIXED);
continue;
}
if (lastMatch != null) {
SolverVariable begin = lastMatch.mListAnchors[offset].mSolverVariable;
SolverVariable end = lastMatch.mListAnchors[offset + 1].mSolverVariable;
SolverVariable nextBegin = match.mListAnchors[offset].mSolverVariable;
SolverVariable nextEnd = match.mListAnchors[offset + 1].mSolverVariable;
ArrayRow row = system.createRow();
row.createRowEqualMatchDimensions(lastWeight, totalWeights, currentWeight,
begin, end, nextBegin, nextEnd);
system.addConstraint(row);
}
lastMatch = match;
lastWeight = currentWeight;
}
}
}
if (DEBUG) {
widget = firstVisibleWidget;
while (widget != null) {
next = widget.mNextChainWidget[orientation];
widget.mListAnchors[offset].mSolverVariable.setName("" + widget.getDebugName() + ".left");
widget.mListAnchors[offset + 1].mSolverVariable.setName("" + widget.getDebugName() + ".right");
widget = next;
}
}
// Finally, let's apply the specific rules dealing with the different chain types
if (firstVisibleWidget != null && (firstVisibleWidget == lastVisibleWidget || isChainPacked)) {
ConstraintAnchor begin = first.mListAnchors[offset];
ConstraintAnchor end = last.mListAnchors[offset + 1];
SolverVariable beginTarget = first.mListAnchors[offset].mTarget != null ? first.mListAnchors[offset].mTarget.mSolverVariable : null;
SolverVariable endTarget = last.mListAnchors[offset + 1].mTarget != null ? last.mListAnchors[offset + 1].mTarget.mSolverVariable : null;
if (firstVisibleWidget == lastVisibleWidget) {
begin = firstVisibleWidget.mListAnchors[offset];
end = firstVisibleWidget.mListAnchors[offset + 1];
}
if (beginTarget != null && endTarget != null) {
float bias = 0.5f;
if (orientation == ConstraintWidget.HORIZONTAL) {
bias = head.mHorizontalBiasPercent;
} else {
bias = head.mVerticalBiasPercent;
}
int beginMargin = begin.getMargin();
int endMargin = end.getMargin();
system.addCentering(begin.mSolverVariable, beginTarget, beginMargin, bias,
endTarget, end.mSolverVariable, endMargin, SolverVariable.STRENGTH_EQUALITY);
}
} else if (isChainSpread && firstVisibleWidget != null) {
// for chain spread, we need to add equal dimensions in between *visible* widgets
widget = firstVisibleWidget;
ConstraintWidget previousVisibleWidget = firstVisibleWidget;
boolean applyFixedEquality = chainHead.mWidgetsMatchCount > 0 && (chainHead.mWidgetsCount == chainHead.mWidgetsMatchCount);
while (widget != null) {
next = widget.mNextChainWidget[orientation];
while (next != null && next.getVisibility() == GONE) {
next = next.mNextChainWidget[orientation];
}
if (next != null || widget == lastVisibleWidget) {
ConstraintAnchor beginAnchor = widget.mListAnchors[offset];
SolverVariable begin = beginAnchor.mSolverVariable;
SolverVariable beginTarget = beginAnchor.mTarget != null ? beginAnchor.mTarget.mSolverVariable : null;
if (previousVisibleWidget != widget) {
beginTarget = previousVisibleWidget.mListAnchors[offset + 1].mSolverVariable;
} else if (widget == firstVisibleWidget && previousVisibleWidget == widget) {
beginTarget = first.mListAnchors[offset].mTarget != null ? first.mListAnchors[offset].mTarget.mSolverVariable : null;
}
ConstraintAnchor beginNextAnchor = null;
SolverVariable beginNext = null;
SolverVariable beginNextTarget = null;
int beginMargin = beginAnchor.getMargin();
int nextMargin = widget.mListAnchors[offset + 1].getMargin();
if (next != null) {
beginNextAnchor = next.mListAnchors[offset];
beginNext = beginNextAnchor.mSolverVariable;
beginNextTarget = widget.mListAnchors[offset + 1].mSolverVariable;
} else {
beginNextAnchor = last.mListAnchors[offset + 1].mTarget;
if (beginNextAnchor != null) {
beginNext = beginNextAnchor.mSolverVariable;
}
beginNextTarget = widget.mListAnchors[offset + 1].mSolverVariable;
}
if (beginNextAnchor != null) {
nextMargin += beginNextAnchor.getMargin();
}
if (previousVisibleWidget != null) {
beginMargin += previousVisibleWidget.mListAnchors[offset + 1].getMargin();
}
if (begin != null && beginTarget != null && beginNext != null && beginNextTarget != null) {
int margin1 = beginMargin;
if (widget == firstVisibleWidget) {
margin1 = firstVisibleWidget.mListAnchors[offset].getMargin();
}
int margin2 = nextMargin;
if (widget == lastVisibleWidget) {
margin2 = lastVisibleWidget.mListAnchors[offset + 1].getMargin();
}
int strength = SolverVariable.STRENGTH_HIGHEST;
if (applyFixedEquality) {
strength = SolverVariable.STRENGTH_FIXED;
}
system.addCentering(begin, beginTarget, margin1, 0.5f,
beginNext, beginNextTarget, margin2,
strength);
}
}
if (widget.getVisibility() != GONE) {
previousVisibleWidget = widget;
}
widget = next;
}
} else if (isChainSpreadInside && firstVisibleWidget != null) {
// for chain spread inside, we need to add equal dimensions in between *visible* widgets
widget = firstVisibleWidget;
ConstraintWidget previousVisibleWidget = firstVisibleWidget;
boolean applyFixedEquality = chainHead.mWidgetsMatchCount > 0 && (chainHead.mWidgetsCount == chainHead.mWidgetsMatchCount);
while (widget != null) {
next = widget.mNextChainWidget[orientation];
while (next != null && next.getVisibility() == GONE) {
next = next.mNextChainWidget[orientation];
}
if (widget != firstVisibleWidget && widget != lastVisibleWidget && next != null) {
if (next == lastVisibleWidget) {
next = null;
}
ConstraintAnchor beginAnchor = widget.mListAnchors[offset];
SolverVariable begin = beginAnchor.mSolverVariable;
SolverVariable beginTarget = beginAnchor.mTarget != null ? beginAnchor.mTarget.mSolverVariable : null;
beginTarget = previousVisibleWidget.mListAnchors[offset + 1].mSolverVariable;
ConstraintAnchor beginNextAnchor = null;
SolverVariable beginNext = null;
SolverVariable beginNextTarget = null;
int beginMargin = beginAnchor.getMargin();
int nextMargin = widget.mListAnchors[offset + 1].getMargin();
if (next != null) {
beginNextAnchor = next.mListAnchors[offset];
beginNext = beginNextAnchor.mSolverVariable;
beginNextTarget = beginNextAnchor.mTarget != null ? beginNextAnchor.mTarget.mSolverVariable : null;
} else {
beginNextAnchor = widget.mListAnchors[offset + 1].mTarget;
if (beginNextAnchor != null) {
beginNext = beginNextAnchor.mSolverVariable;
}
beginNextTarget = widget.mListAnchors[offset + 1].mSolverVariable;
}
if (beginNextAnchor != null) {
nextMargin += beginNextAnchor.getMargin();
}
if (previousVisibleWidget != null) {
beginMargin += previousVisibleWidget.mListAnchors[offset + 1].getMargin();
}
int strength = SolverVariable.STRENGTH_HIGHEST;
if (applyFixedEquality) {
strength = SolverVariable.STRENGTH_FIXED;
}
if (begin != null && beginTarget != null && beginNext != null && beginNextTarget != null) {
system.addCentering(begin, beginTarget, beginMargin, 0.5f,
beginNext, beginNextTarget, nextMargin,
strength);
}
}
if (widget.getVisibility() != GONE) {
previousVisibleWidget = widget;
}
widget = next;
}
ConstraintAnchor begin = firstVisibleWidget.mListAnchors[offset];
ConstraintAnchor beginTarget = first.mListAnchors[offset].mTarget;
ConstraintAnchor end = lastVisibleWidget.mListAnchors[offset + 1];
ConstraintAnchor endTarget = last.mListAnchors[offset + 1].mTarget;
if (beginTarget != null) {
if (firstVisibleWidget != lastVisibleWidget) {
system.addEquality(begin.mSolverVariable, beginTarget.mSolverVariable, begin.getMargin(), SolverVariable.STRENGTH_EQUALITY);
} else if (endTarget != null) {
system.addCentering(begin.mSolverVariable, beginTarget.mSolverVariable, begin.getMargin(), 0.5f,
end.mSolverVariable, endTarget.mSolverVariable, end.getMargin(), SolverVariable.STRENGTH_EQUALITY);
}
}
if (endTarget != null && (firstVisibleWidget != lastVisibleWidget)) {
system.addEquality(end.mSolverVariable, endTarget.mSolverVariable, -end.getMargin(), SolverVariable.STRENGTH_EQUALITY);
}
}
// final centering, necessary if the chain is larger than the available space...
if ((isChainSpread || isChainSpreadInside) && firstVisibleWidget != null) {
ConstraintAnchor begin = firstVisibleWidget.mListAnchors[offset];
ConstraintAnchor end = lastVisibleWidget.mListAnchors[offset + 1];
SolverVariable beginTarget = begin.mTarget != null ? begin.mTarget.mSolverVariable : null;
SolverVariable endTarget = end.mTarget != null ? end.mTarget.mSolverVariable : null;
if (last != lastVisibleWidget) {
ConstraintAnchor realEnd = last.mListAnchors[offset + 1];
endTarget = realEnd.mTarget != null ? realEnd.mTarget.mSolverVariable : null;
}
if (firstVisibleWidget == lastVisibleWidget) {
begin = firstVisibleWidget.mListAnchors[offset];
end = firstVisibleWidget.mListAnchors[offset + 1];
}
if (beginTarget != null && endTarget != null) {
float bias = 0.5f;
int beginMargin = begin.getMargin();
if (lastVisibleWidget == null) {
// everything is hidden
lastVisibleWidget = last;
}
int endMargin = lastVisibleWidget.mListAnchors[offset + 1].getMargin();
system.addCentering(begin.mSolverVariable, beginTarget, beginMargin, bias,
endTarget, end.mSolverVariable, endMargin, SolverVariable.STRENGTH_EQUALITY);
}
}
}
}