1 /*
2  * Copyright (C) 2017 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 androidx.constraintlayout.core.LinearSystem;
20 import androidx.constraintlayout.core.SolverVariable;
21 
22 import java.util.HashMap;
23 
24 /**
25  * A Barrier takes multiple widgets
26  */
27 public class Barrier extends HelperWidget {
28 
29     public static final int LEFT = 0;
30     public static final int RIGHT = 1;
31     public static final int TOP = 2;
32     public static final int BOTTOM = 3;
33     private static final boolean USE_RESOLUTION = true;
34     private static final boolean USE_RELAX_GONE = false;
35 
36     private int mBarrierType = LEFT;
37 
38     private boolean mAllowsGoneWidget = true;
39     private int mMargin = 0;
40     boolean mResolved = false;
41 
Barrier()42     public Barrier() {
43     }
44 
Barrier(String debugName)45     public Barrier(String debugName) {
46         setDebugName(debugName);
47     }
48 
49     @Override
allowedInBarrier()50     public boolean allowedInBarrier() {
51         return true;
52     }
53 
getBarrierType()54     public int getBarrierType() {
55         return mBarrierType;
56     }
57 
setBarrierType(int barrierType)58     public void setBarrierType(int barrierType) {
59         mBarrierType = barrierType;
60     }
61 
setAllowsGoneWidget(boolean allowsGoneWidget)62     public void setAllowsGoneWidget(boolean allowsGoneWidget) {
63         mAllowsGoneWidget = allowsGoneWidget;
64     }
65 
66     /**
67      * Find if this barrier supports gone widgets.
68      *
69      * @return true if this barrier supports gone widgets, otherwise false
70      * @deprecated This method should be called {@code getAllowsGoneWidget}
71      * such that {@code allowsGoneWidget}
72      * can be accessed as a property from Kotlin; {@see https://android.github
73      * .io/kotlin-guides/interop.html#property-prefixes}.
74      * Use {@link #getAllowsGoneWidget()} instead.
75      */
76     @Deprecated
allowsGoneWidget()77     public boolean allowsGoneWidget() {
78         return mAllowsGoneWidget;
79     }
80 
81     /**
82      * Find if this barrier supports gone widgets.
83      *
84      * @return true if this barrier supports gone widgets, otherwise false
85      */
getAllowsGoneWidget()86     public boolean getAllowsGoneWidget() {
87         return mAllowsGoneWidget;
88     }
89 
90     @Override
isResolvedHorizontally()91     public boolean isResolvedHorizontally() {
92         return mResolved;
93     }
94 
95     @Override
isResolvedVertically()96     public boolean isResolvedVertically() {
97         return mResolved;
98     }
99 
100     @Override
copy(ConstraintWidget src, HashMap<ConstraintWidget, ConstraintWidget> map)101     public void copy(ConstraintWidget src, HashMap<ConstraintWidget, ConstraintWidget> map) {
102         super.copy(src, map);
103         Barrier srcBarrier = (Barrier) src;
104         mBarrierType = srcBarrier.mBarrierType;
105         mAllowsGoneWidget = srcBarrier.mAllowsGoneWidget;
106         mMargin = srcBarrier.mMargin;
107     }
108 
109     @Override
toString()110     public String toString() {
111         String debug = "[Barrier] " + getDebugName() + " {";
112         for (int i = 0; i < mWidgetsCount; i++) {
113             ConstraintWidget widget = mWidgets[i];
114             if (i > 0) {
115                 debug += ", ";
116             }
117             debug += widget.getDebugName();
118         }
119         debug += "}";
120         return debug;
121     }
122 
markWidgets()123     protected void markWidgets() {
124         for (int i = 0; i < mWidgetsCount; i++) {
125             ConstraintWidget widget = mWidgets[i];
126             if (!mAllowsGoneWidget && !widget.allowedInBarrier()) {
127                 continue;
128             }
129             if (mBarrierType == LEFT || mBarrierType == RIGHT) {
130                 widget.setInBarrier(HORIZONTAL, true);
131             } else if (mBarrierType == TOP || mBarrierType == BOTTOM) {
132                 widget.setInBarrier(VERTICAL, true);
133             }
134         }
135     }
136 
137     /**
138      * Add this widget to the solver
139      *
140      * @param system   the solver we want to add the widget to
141      * @param optimize true if {@link Optimizer#OPTIMIZATION_GRAPH} is on
142      */
143     @Override
addToSolver(LinearSystem system, boolean optimize)144     public void addToSolver(LinearSystem system, boolean optimize) {
145         if (LinearSystem.FULL_DEBUG) {
146             System.out.println("\n----------------------------------------------");
147             System.out.println("-- adding " + getDebugName() + " to the solver");
148             System.out.println("----------------------------------------------\n");
149         }
150 
151         ConstraintAnchor position;
152         mListAnchors[LEFT] = mLeft;
153         mListAnchors[TOP] = mTop;
154         mListAnchors[RIGHT] = mRight;
155         mListAnchors[BOTTOM] = mBottom;
156         for (int i = 0; i < mListAnchors.length; i++) {
157             mListAnchors[i].mSolverVariable = system.createObjectVariable(mListAnchors[i]);
158         }
159         if (mBarrierType >= 0 && mBarrierType < 4) {
160             position = mListAnchors[mBarrierType];
161         } else {
162             return;
163         }
164 
165         if (USE_RESOLUTION) {
166             if (!mResolved) {
167                 allSolved();
168             }
169             if (mResolved) {
170                 mResolved = false;
171                 if (mBarrierType == LEFT || mBarrierType == RIGHT) {
172                     system.addEquality(mLeft.mSolverVariable, mX);
173                     system.addEquality(mRight.mSolverVariable, mX);
174                 } else if (mBarrierType == TOP || mBarrierType == BOTTOM) {
175                     system.addEquality(mTop.mSolverVariable, mY);
176                     system.addEquality(mBottom.mSolverVariable, mY);
177                 }
178                 return;
179             }
180         }
181 
182         // We have to handle the case where some of the elements
183         //  referenced in the barrier are set as
184         // match_constraint; we have to take it in account to set the strength of the barrier.
185         boolean hasMatchConstraintWidgets = false;
186         for (int i = 0; i < mWidgetsCount; i++) {
187             ConstraintWidget widget = mWidgets[i];
188             if (!mAllowsGoneWidget && !widget.allowedInBarrier()) {
189                 continue;
190             }
191             if ((mBarrierType == LEFT || mBarrierType == RIGHT)
192                     && (widget.getHorizontalDimensionBehaviour()
193                     == DimensionBehaviour.MATCH_CONSTRAINT)
194                     && widget.mLeft.mTarget != null && widget.mRight.mTarget != null) {
195                 hasMatchConstraintWidgets = true;
196                 break;
197             } else if ((mBarrierType == TOP || mBarrierType == BOTTOM)
198                     && (widget.getVerticalDimensionBehaviour()
199                     == DimensionBehaviour.MATCH_CONSTRAINT)
200                     && widget.mTop.mTarget != null && widget.mBottom.mTarget != null) {
201                 hasMatchConstraintWidgets = true;
202                 break;
203             }
204         }
205 
206         boolean mHasHorizontalCenteredDependents =
207                 mLeft.hasCenteredDependents() || mRight.hasCenteredDependents();
208         boolean mHasVerticalCenteredDependents =
209                 mTop.hasCenteredDependents() || mBottom.hasCenteredDependents();
210         boolean applyEqualityOnReferences = !hasMatchConstraintWidgets
211                 && ((mBarrierType == LEFT && mHasHorizontalCenteredDependents)
212                 || (mBarrierType == TOP && mHasVerticalCenteredDependents)
213                 || (mBarrierType == RIGHT && mHasHorizontalCenteredDependents)
214                 || (mBarrierType == BOTTOM && mHasVerticalCenteredDependents));
215 
216         int equalityOnReferencesStrength = SolverVariable.STRENGTH_EQUALITY;
217         if (!applyEqualityOnReferences) {
218             equalityOnReferencesStrength = SolverVariable.STRENGTH_HIGHEST;
219         }
220         for (int i = 0; i < mWidgetsCount; i++) {
221             ConstraintWidget widget = mWidgets[i];
222             if (!mAllowsGoneWidget && !widget.allowedInBarrier()) {
223                 continue;
224             }
225             SolverVariable target = system.createObjectVariable(widget.mListAnchors[mBarrierType]);
226             widget.mListAnchors[mBarrierType].mSolverVariable = target;
227             int margin = 0;
228             if (widget.mListAnchors[mBarrierType].mTarget != null
229                     && widget.mListAnchors[mBarrierType].mTarget.mOwner == this) {
230                 margin += widget.mListAnchors[mBarrierType].mMargin;
231             }
232             if (mBarrierType == LEFT || mBarrierType == TOP) {
233                 system.addLowerBarrier(position.mSolverVariable, target,
234                         mMargin - margin, hasMatchConstraintWidgets);
235             } else {
236                 system.addGreaterBarrier(position.mSolverVariable, target,
237                         mMargin + margin, hasMatchConstraintWidgets);
238             }
239             if (USE_RELAX_GONE) {
240                 if (widget.getVisibility() != GONE
241                         || widget instanceof Guideline || widget instanceof Barrier) {
242                     system.addEquality(position.mSolverVariable, target,
243                             mMargin + margin, equalityOnReferencesStrength);
244                 }
245             } else {
246                 system.addEquality(position.mSolverVariable, target,
247                         mMargin + margin, equalityOnReferencesStrength);
248             }
249         }
250 
251         int barrierParentStrength = SolverVariable.STRENGTH_HIGHEST;
252         int barrierParentStrengthOpposite = SolverVariable.STRENGTH_NONE;
253 
254         if (mBarrierType == LEFT) {
255             system.addEquality(mRight.mSolverVariable,
256                     mLeft.mSolverVariable, 0, SolverVariable.STRENGTH_FIXED);
257             system.addEquality(mLeft.mSolverVariable,
258                     mParent.mRight.mSolverVariable, 0, barrierParentStrength);
259             system.addEquality(mLeft.mSolverVariable,
260                     mParent.mLeft.mSolverVariable, 0, barrierParentStrengthOpposite);
261         } else if (mBarrierType == RIGHT) {
262             system.addEquality(mLeft.mSolverVariable,
263                     mRight.mSolverVariable, 0, SolverVariable.STRENGTH_FIXED);
264             system.addEquality(mLeft.mSolverVariable,
265                     mParent.mLeft.mSolverVariable, 0, barrierParentStrength);
266             system.addEquality(mLeft.mSolverVariable,
267                     mParent.mRight.mSolverVariable, 0, barrierParentStrengthOpposite);
268         } else if (mBarrierType == TOP) {
269             system.addEquality(mBottom.mSolverVariable,
270                     mTop.mSolverVariable, 0, SolverVariable.STRENGTH_FIXED);
271             system.addEquality(mTop.mSolverVariable,
272                     mParent.mBottom.mSolverVariable, 0, barrierParentStrength);
273             system.addEquality(mTop.mSolverVariable,
274                     mParent.mTop.mSolverVariable, 0, barrierParentStrengthOpposite);
275         } else if (mBarrierType == BOTTOM) {
276             system.addEquality(mTop.mSolverVariable,
277                     mBottom.mSolverVariable, 0, SolverVariable.STRENGTH_FIXED);
278             system.addEquality(mTop.mSolverVariable,
279                     mParent.mTop.mSolverVariable, 0, barrierParentStrength);
280             system.addEquality(mTop.mSolverVariable,
281                     mParent.mBottom.mSolverVariable, 0, barrierParentStrengthOpposite);
282         }
283     }
284 
setMargin(int margin)285     public void setMargin(int margin) {
286         mMargin = margin;
287     }
288 
getMargin()289     public int getMargin() {
290         return mMargin;
291     }
292 
293     // @TODO: add description
getOrientation()294     public int getOrientation() {
295         switch (mBarrierType) {
296             case LEFT:
297             case RIGHT:
298                 return HORIZONTAL;
299             case TOP:
300             case BOTTOM:
301                 return VERTICAL;
302         }
303         return UNKNOWN;
304     }
305 
306     // @TODO: add description
allSolved()307     public boolean allSolved() {
308         if (!USE_RESOLUTION) {
309             return false;
310         }
311         boolean hasAllWidgetsResolved = true;
312         for (int i = 0; i < mWidgetsCount; i++) {
313             ConstraintWidget widget = mWidgets[i];
314             if (!mAllowsGoneWidget && !widget.allowedInBarrier()) {
315                 continue;
316             }
317             if ((mBarrierType == LEFT || mBarrierType == RIGHT)
318                     && !widget.isResolvedHorizontally()) {
319                 hasAllWidgetsResolved = false;
320             } else if ((mBarrierType == TOP || mBarrierType == BOTTOM)
321                     && !widget.isResolvedVertically()) {
322                 hasAllWidgetsResolved = false;
323             }
324         }
325 
326         if (hasAllWidgetsResolved && mWidgetsCount > 0) {
327             // we're done!
328             int barrierPosition = 0;
329             boolean initialized = false;
330             for (int i = 0; i < mWidgetsCount; i++) {
331                 ConstraintWidget widget = mWidgets[i];
332                 if (!mAllowsGoneWidget && !widget.allowedInBarrier()) {
333                     continue;
334                 }
335                 if (!initialized) {
336                     if (mBarrierType == LEFT) {
337                         barrierPosition =
338                                 widget.getAnchor(ConstraintAnchor.Type.LEFT).getFinalValue();
339                     } else if (mBarrierType == RIGHT) {
340                         barrierPosition =
341                                 widget.getAnchor(ConstraintAnchor.Type.RIGHT).getFinalValue();
342                     } else if (mBarrierType == TOP) {
343                         barrierPosition =
344                                 widget.getAnchor(ConstraintAnchor.Type.TOP).getFinalValue();
345                     } else if (mBarrierType == BOTTOM) {
346                         barrierPosition =
347                                 widget.getAnchor(ConstraintAnchor.Type.BOTTOM).getFinalValue();
348                     }
349                     initialized = true;
350                 }
351                 if (mBarrierType == LEFT) {
352                     barrierPosition = Math.min(barrierPosition,
353                             widget.getAnchor(ConstraintAnchor.Type.LEFT).getFinalValue());
354                 } else if (mBarrierType == RIGHT) {
355                     barrierPosition = Math.max(barrierPosition,
356                             widget.getAnchor(ConstraintAnchor.Type.RIGHT).getFinalValue());
357                 } else if (mBarrierType == TOP) {
358                     barrierPosition = Math.min(barrierPosition,
359                             widget.getAnchor(ConstraintAnchor.Type.TOP).getFinalValue());
360                 } else if (mBarrierType == BOTTOM) {
361                     barrierPosition = Math.max(barrierPosition,
362                             widget.getAnchor(ConstraintAnchor.Type.BOTTOM).getFinalValue());
363                 }
364             }
365             barrierPosition += mMargin;
366             if (mBarrierType == LEFT || mBarrierType == RIGHT) {
367                 setFinalHorizontal(barrierPosition, barrierPosition);
368             } else {
369                 setFinalVertical(barrierPosition, barrierPosition);
370             }
371             if (LinearSystem.FULL_DEBUG) {
372                 System.out.println("*** BARRIER " + getDebugName()
373                         + " SOLVED TO " + barrierPosition + " ***");
374             }
375             mResolved = true;
376             return true;
377         }
378         return false;
379     }
380 }
381