/*
* 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 androidx.constraintlayout.core.widgets;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.GONE;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_SPREAD;
import androidx.constraintlayout.core.ArrayRow;
import androidx.constraintlayout.core.LinearSystem;
import androidx.constraintlayout.core.SolverVariable;
import androidx.constraintlayout.core.widgets.analyzer.Direct;
import java.util.ArrayList;
/**
* Chain management and constraints creation
*/
public class Chain {
private static final boolean DEBUG = false;
public static final boolean USE_CHAIN_OPTIMIZATION = 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
*/
public static void applyChainConstraints(
ConstraintWidgetContainer constraintWidgetContainer,
LinearSystem system,
ArrayList<ConstraintWidget> widgets,
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 (widgets == null || widgets.contains(first.mFirst)) {
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 accommodate 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;
@SuppressWarnings("unused") ConstraintWidget firstMatchConstraintsWidget =
chainHead.mFirstMatchConstraintWidget;
@SuppressWarnings("unused") 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;
}
if (USE_CHAIN_OPTIMIZATION && !isWrapContent
&& Direct.solveChain(container, system, orientation, offset, chainHead,
isChainSpread, isChainSpreadInside, isChainPacked)) {
if (LinearSystem.FULL_DEBUG) {
System.out.println("### CHAIN FULLY SOLVED! ###");
}
return; // done with the chain!
} else if (LinearSystem.FULL_DEBUG) {
System.out.println("### CHAIN WASN'T SOLVED DIRECTLY... ###");
}
// This 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 (isChainPacked) {
strength = SolverVariable.STRENGTH_LOW;
}
int margin = begin.getMargin();
boolean isSpreadOnly = widget.mListDimensionBehaviors[orientation]
== DimensionBehaviour.MATCH_CONSTRAINT
&& widget.mResolvedMatchConstraintDefault[orientation]
== MATCH_CONSTRAINT_SPREAD;
if (begin.mTarget != null && widget != first) {
margin += begin.mTarget.getMargin();
}
if (isChainPacked && widget != first && widget != firstVisibleWidget) {
strength = SolverVariable.STRENGTH_FIXED;
}
if (begin.mTarget != null) {
if (widget == firstVisibleWidget) {
system.addGreaterThan(begin.mSolverVariable, begin.mTarget.mSolverVariable,
margin, SolverVariable.STRENGTH_BARRIER);
} else {
system.addGreaterThan(begin.mSolverVariable, begin.mTarget.mSolverVariable,
margin, SolverVariable.STRENGTH_FIXED);
}
if (isSpreadOnly && !isChainPacked) {
strength = SolverVariable.STRENGTH_EQUALITY;
}
if (widget == firstVisibleWidget && isChainPacked
&& widget.isInBarrier(orientation)) {
strength = SolverVariable.STRENGTH_EQUALITY;
}
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];
boolean isSpreadOnly = lastVisibleWidget.mListDimensionBehaviors[orientation]
== DimensionBehaviour.MATCH_CONSTRAINT
&& lastVisibleWidget.mResolvedMatchConstraintDefault[orientation]
== MATCH_CONSTRAINT_SPREAD;
if (isSpreadOnly && !isChainPacked && end.mTarget.mOwner == container) {
system.addEquality(end.mSolverVariable, end.mTarget.mSolverVariable,
-end.getMargin(), SolverVariable.STRENGTH_EQUALITY);
} else if (isChainPacked && end.mTarget.mOwner == container) {
system.addEquality(end.mSolverVariable, end.mTarget.mSolverVariable,
-end.getMargin(), SolverVariable.STRENGTH_HIGHEST);
}
system.addLowerThan(end.mSolverVariable,
last.mListAnchors[offset + 1].mTarget.mSolverVariable, -end.getMargin(),
SolverVariable.STRENGTH_BARRIER);
}
// ... 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 = begin.mTarget != null
? begin.mTarget.mSolverVariable : null;
SolverVariable endTarget = end.mTarget != null ? end.mTarget.mSolverVariable : null;
begin = firstVisibleWidget.mListAnchors[offset];
if (lastVisibleWidget != null) {
end = lastVisibleWidget.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_CENTERING);
}
} 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) {
beginTarget = first.mListAnchors[offset].mTarget != null
? first.mListAnchors[offset].mTarget.mSolverVariable : null;
}
ConstraintAnchor beginNextAnchor = null;
SolverVariable beginNext = null;
@SuppressWarnings("unused") SolverVariable beginNextTarget = null;
int beginMargin = beginAnchor.getMargin();
int nextMargin = widget.mListAnchors[offset + 1].getMargin();
if (next != null) {
beginNextAnchor = next.mListAnchors[offset];
beginNext = beginNextAnchor.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();
}
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_EQUALITY;
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;
@SuppressWarnings("unused") 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 = lastVisibleWidget.mListAnchors[offset];
if (beginNextAnchor != null) {
beginNext = beginNextAnchor.mSolverVariable;
}
beginNextTarget = widget.mListAnchors[offset + 1].mSolverVariable;
}
if (beginNextAnchor != null) {
nextMargin += beginNextAnchor.getMargin();
}
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;
int endPointsStrength = SolverVariable.STRENGTH_EQUALITY;
if (beginTarget != null) {
if (firstVisibleWidget != lastVisibleWidget) {
system.addEquality(begin.mSolverVariable, beginTarget.mSolverVariable,
begin.getMargin(), endPointsStrength);
} else if (endTarget != null) {
system.addCentering(begin.mSolverVariable, beginTarget.mSolverVariable,
begin.getMargin(), 0.5f, end.mSolverVariable, endTarget.mSolverVariable,
end.getMargin(), endPointsStrength);
}
}
if (endTarget != null && (firstVisibleWidget != lastVisibleWidget)) {
system.addEquality(end.mSolverVariable,
endTarget.mSolverVariable, -end.getMargin(), endPointsStrength);
}
}
// final centering, necessary if the chain is larger than the available space...
if ((isChainSpread || isChainSpreadInside) && firstVisibleWidget
!= null && firstVisibleWidget != lastVisibleWidget) {
ConstraintAnchor begin = firstVisibleWidget.mListAnchors[offset];
if (lastVisibleWidget == null) {
lastVisibleWidget = firstVisibleWidget;
}
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();
int endMargin = lastVisibleWidget.mListAnchors[offset + 1].getMargin();
system.addCentering(begin.mSolverVariable, beginTarget, beginMargin,
bias, endTarget, end.mSolverVariable, endMargin,
SolverVariable.STRENGTH_EQUALITY);
}
}
}
}