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 static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour;
20 import static androidx.constraintlayout.core.widgets.ConstraintWidget.GONE;
21 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_SPREAD;
22 
23 import androidx.constraintlayout.core.ArrayRow;
24 import androidx.constraintlayout.core.LinearSystem;
25 import androidx.constraintlayout.core.SolverVariable;
26 import androidx.constraintlayout.core.widgets.analyzer.Direct;
27 
28 import java.util.ArrayList;
29 
30 /**
31  * Chain management and constraints creation
32  */
33 public class Chain {
34 
35     private static final boolean DEBUG = false;
36     public static final boolean USE_CHAIN_OPTIMIZATION = false;
37 
38     /**
39      * Apply specific rules for dealing with chains of widgets.
40      * Chains are defined as a list of widget linked together with bi-directional connections
41      *
42      * @param constraintWidgetContainer root container
43      * @param system                    the linear system we add the equations to
44      * @param widgets                   if this is null or contains any chainheads' widgets,
45      *                                  constrain all chains / the corresponding chains
46      * @param orientation               HORIZONTAL or VERTICAL
47      */
applyChainConstraints( ConstraintWidgetContainer constraintWidgetContainer, LinearSystem system, ArrayList<ConstraintWidget> widgets, int orientation)48     public static void applyChainConstraints(
49             ConstraintWidgetContainer constraintWidgetContainer,
50             LinearSystem system,
51             ArrayList<ConstraintWidget> widgets,
52             int orientation) {
53         // what to do:
54         // Don't skip things. Either the element is GONE or not.
55         int offset = 0;
56         int chainsSize = 0;
57         ChainHead[] chainsArray = null;
58         if (orientation == ConstraintWidget.HORIZONTAL) {
59             offset = 0;
60             chainsSize = constraintWidgetContainer.mHorizontalChainsSize;
61             chainsArray = constraintWidgetContainer.mHorizontalChainsArray;
62         } else {
63             offset = 2;
64             chainsSize = constraintWidgetContainer.mVerticalChainsSize;
65             chainsArray = constraintWidgetContainer.mVerticalChainsArray;
66         }
67 
68         for (int i = 0; i < chainsSize; i++) {
69             ChainHead first = chainsArray[i];
70             // we have to make sure we define the ChainHead here,
71             // otherwise the values we use may not be correctly initialized
72             // (as we initialize them in the ConstraintWidget.addToSolver())
73             first.define();
74             if (widgets == null || widgets.contains(first.mFirst)) {
75                 applyChainConstraints(constraintWidgetContainer,
76                         system, orientation, offset, first);
77             }
78         }
79     }
80 
81     /**
82      * Apply specific rules for dealing with chains of widgets.
83      * Chains are defined as a list of widget linked together with bi-directional connections
84      *
85      * @param container   the root container
86      * @param system      the linear system we add the equations to
87      * @param orientation HORIZONTAL or VERTICAL
88      * @param offset      0 or 2 to accommodate for HORIZONTAL / VERTICAL
89      * @param chainHead   a chain represented by its main elements
90      */
applyChainConstraints(ConstraintWidgetContainer container, LinearSystem system, int orientation, int offset, ChainHead chainHead)91     static void applyChainConstraints(ConstraintWidgetContainer container, LinearSystem system,
92             int orientation, int offset, ChainHead chainHead) {
93         ConstraintWidget first = chainHead.mFirst;
94         ConstraintWidget last = chainHead.mLast;
95         ConstraintWidget firstVisibleWidget = chainHead.mFirstVisibleWidget;
96         ConstraintWidget lastVisibleWidget = chainHead.mLastVisibleWidget;
97         ConstraintWidget head = chainHead.mHead;
98 
99         ConstraintWidget widget = first;
100         ConstraintWidget next = null;
101         boolean done = false;
102 
103         float totalWeights = chainHead.mTotalWeight;
104         @SuppressWarnings("unused") ConstraintWidget firstMatchConstraintsWidget =
105                 chainHead.mFirstMatchConstraintWidget;
106         @SuppressWarnings("unused") ConstraintWidget previousMatchConstraintsWidget =
107                 chainHead.mLastMatchConstraintWidget;
108 
109         boolean isWrapContent = container.mListDimensionBehaviors[orientation]
110                 == ConstraintWidget.DimensionBehaviour.WRAP_CONTENT;
111         boolean isChainSpread = false;
112         boolean isChainSpreadInside = false;
113         boolean isChainPacked = false;
114 
115         if (orientation == ConstraintWidget.HORIZONTAL) {
116             isChainSpread = head.mHorizontalChainStyle == ConstraintWidget.CHAIN_SPREAD;
117             isChainSpreadInside =
118                     head.mHorizontalChainStyle == ConstraintWidget.CHAIN_SPREAD_INSIDE;
119             isChainPacked = head.mHorizontalChainStyle == ConstraintWidget.CHAIN_PACKED;
120         } else {
121             isChainSpread = head.mVerticalChainStyle == ConstraintWidget.CHAIN_SPREAD;
122             isChainSpreadInside = head.mVerticalChainStyle == ConstraintWidget.CHAIN_SPREAD_INSIDE;
123             isChainPacked = head.mVerticalChainStyle == ConstraintWidget.CHAIN_PACKED;
124         }
125 
126         if (USE_CHAIN_OPTIMIZATION && !isWrapContent
127                 && Direct.solveChain(container, system, orientation, offset, chainHead,
128                 isChainSpread, isChainSpreadInside, isChainPacked)) {
129             if (LinearSystem.FULL_DEBUG) {
130                 System.out.println("### CHAIN FULLY SOLVED! ###");
131             }
132             return; // done with the chain!
133         } else if (LinearSystem.FULL_DEBUG) {
134             System.out.println("### CHAIN WASN'T SOLVED DIRECTLY... ###");
135         }
136 
137         // This traversal will:
138         // - set up some basic ordering constraints
139         // - build a linked list of matched constraints widgets
140         while (!done) {
141             ConstraintAnchor begin = widget.mListAnchors[offset];
142 
143             int strength = SolverVariable.STRENGTH_HIGHEST;
144             if (isChainPacked) {
145                 strength = SolverVariable.STRENGTH_LOW;
146             }
147             int margin = begin.getMargin();
148             boolean isSpreadOnly = widget.mListDimensionBehaviors[orientation]
149                     == DimensionBehaviour.MATCH_CONSTRAINT
150                     && widget.mResolvedMatchConstraintDefault[orientation]
151                     == MATCH_CONSTRAINT_SPREAD;
152 
153             if (begin.mTarget != null && widget != first) {
154                 margin += begin.mTarget.getMargin();
155             }
156 
157             if (isChainPacked && widget != first && widget != firstVisibleWidget) {
158                 strength = SolverVariable.STRENGTH_FIXED;
159             }
160 
161             if (begin.mTarget != null) {
162                 if (widget == firstVisibleWidget) {
163                     system.addGreaterThan(begin.mSolverVariable, begin.mTarget.mSolverVariable,
164                             margin, SolverVariable.STRENGTH_BARRIER);
165                 } else {
166                     system.addGreaterThan(begin.mSolverVariable, begin.mTarget.mSolverVariable,
167                             margin, SolverVariable.STRENGTH_FIXED);
168                 }
169                 if (isSpreadOnly && !isChainPacked) {
170                     strength = SolverVariable.STRENGTH_EQUALITY;
171                 }
172                 if (widget == firstVisibleWidget && isChainPacked
173                         && widget.isInBarrier(orientation)) {
174                     strength = SolverVariable.STRENGTH_EQUALITY;
175                 }
176                 system.addEquality(begin.mSolverVariable, begin.mTarget.mSolverVariable, margin,
177                         strength);
178             }
179 
180             if (isWrapContent) {
181                 if (widget.getVisibility() != ConstraintWidget.GONE
182                         && widget.mListDimensionBehaviors[orientation]
183                         == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT) {
184                     system.addGreaterThan(widget.mListAnchors[offset + 1].mSolverVariable,
185                             widget.mListAnchors[offset].mSolverVariable, 0,
186                             SolverVariable.STRENGTH_EQUALITY);
187                 }
188                 system.addGreaterThan(widget.mListAnchors[offset].mSolverVariable,
189                         container.mListAnchors[offset].mSolverVariable,
190                         0, SolverVariable.STRENGTH_FIXED);
191             }
192 
193             // go to the next widget
194             ConstraintAnchor nextAnchor = widget.mListAnchors[offset + 1].mTarget;
195             if (nextAnchor != null) {
196                 next = nextAnchor.mOwner;
197                 if (next.mListAnchors[offset].mTarget == null
198                         || next.mListAnchors[offset].mTarget.mOwner != widget) {
199                     next = null;
200                 }
201             } else {
202                 next = null;
203             }
204             if (next != null) {
205                 widget = next;
206             } else {
207                 done = true;
208             }
209         }
210 
211         // Make sure we have constraints for the last anchors / targets
212         if (lastVisibleWidget != null && last.mListAnchors[offset + 1].mTarget != null) {
213             ConstraintAnchor end = lastVisibleWidget.mListAnchors[offset + 1];
214             boolean isSpreadOnly = lastVisibleWidget.mListDimensionBehaviors[orientation]
215                     == DimensionBehaviour.MATCH_CONSTRAINT
216                     && lastVisibleWidget.mResolvedMatchConstraintDefault[orientation]
217                     == MATCH_CONSTRAINT_SPREAD;
218             if (isSpreadOnly && !isChainPacked && end.mTarget.mOwner == container) {
219                 system.addEquality(end.mSolverVariable, end.mTarget.mSolverVariable,
220                         -end.getMargin(), SolverVariable.STRENGTH_EQUALITY);
221             } else if (isChainPacked && end.mTarget.mOwner == container) {
222                 system.addEquality(end.mSolverVariable, end.mTarget.mSolverVariable,
223                         -end.getMargin(), SolverVariable.STRENGTH_HIGHEST);
224             }
225             system.addLowerThan(end.mSolverVariable,
226                     last.mListAnchors[offset + 1].mTarget.mSolverVariable, -end.getMargin(),
227                     SolverVariable.STRENGTH_BARRIER);
228         }
229 
230         // ... and make sure the root end is constrained in wrap content.
231         if (isWrapContent) {
232             system.addGreaterThan(container.mListAnchors[offset + 1].mSolverVariable,
233                     last.mListAnchors[offset + 1].mSolverVariable,
234                     last.mListAnchors[offset + 1].getMargin(), SolverVariable.STRENGTH_FIXED);
235         }
236 
237         // Now, let's apply the centering / spreading for matched constraints widgets
238         ArrayList<ConstraintWidget> listMatchConstraints =
239                 chainHead.mWeightedMatchConstraintsWidgets;
240         if (listMatchConstraints != null) {
241             final int count = listMatchConstraints.size();
242             if (count > 1) {
243                 ConstraintWidget lastMatch = null;
244                 float lastWeight = 0;
245 
246                 if (chainHead.mHasUndefinedWeights && !chainHead.mHasComplexMatchWeights) {
247                     totalWeights = chainHead.mWidgetsMatchCount;
248                 }
249 
250                 for (int i = 0; i < count; i++) {
251                     ConstraintWidget match = listMatchConstraints.get(i);
252                     float currentWeight = match.mWeight[orientation];
253 
254                     if (currentWeight < 0) {
255                         if (chainHead.mHasComplexMatchWeights) {
256                             system.addEquality(match.mListAnchors[offset + 1].mSolverVariable,
257                                     match.mListAnchors[offset].mSolverVariable,
258                                     0, SolverVariable.STRENGTH_HIGHEST);
259                             continue;
260                         }
261                         currentWeight = 1;
262                     }
263                     if (currentWeight == 0) {
264                         system.addEquality(match.mListAnchors[offset + 1].mSolverVariable,
265                                 match.mListAnchors[offset].mSolverVariable,
266                                 0, SolverVariable.STRENGTH_FIXED);
267                         continue;
268                     }
269 
270                     if (lastMatch != null) {
271                         SolverVariable begin = lastMatch.mListAnchors[offset].mSolverVariable;
272                         SolverVariable end = lastMatch.mListAnchors[offset + 1].mSolverVariable;
273                         SolverVariable nextBegin = match.mListAnchors[offset].mSolverVariable;
274                         SolverVariable nextEnd = match.mListAnchors[offset + 1].mSolverVariable;
275                         ArrayRow row = system.createRow();
276                         row.createRowEqualMatchDimensions(lastWeight, totalWeights, currentWeight,
277                                 begin, end, nextBegin, nextEnd);
278                         system.addConstraint(row);
279                     }
280 
281                     lastMatch = match;
282                     lastWeight = currentWeight;
283                 }
284             }
285         }
286 
287         if (DEBUG) {
288             widget = firstVisibleWidget;
289             while (widget != null) {
290                 next = widget.mNextChainWidget[orientation];
291                 widget.mListAnchors[offset].mSolverVariable
292                         .setName("" + widget.getDebugName() + ".left");
293                 widget.mListAnchors[offset + 1].mSolverVariable
294                         .setName("" + widget.getDebugName() + ".right");
295                 widget = next;
296             }
297         }
298 
299         // Finally, let's apply the specific rules dealing with the different chain types
300 
301         if (firstVisibleWidget != null
302                 && (firstVisibleWidget == lastVisibleWidget || isChainPacked)) {
303             ConstraintAnchor begin = first.mListAnchors[offset];
304             ConstraintAnchor end = last.mListAnchors[offset + 1];
305             SolverVariable beginTarget = begin.mTarget != null
306                     ? begin.mTarget.mSolverVariable : null;
307             SolverVariable endTarget = end.mTarget != null ? end.mTarget.mSolverVariable : null;
308             begin = firstVisibleWidget.mListAnchors[offset];
309             if (lastVisibleWidget != null) {
310                 end = lastVisibleWidget.mListAnchors[offset + 1];
311             }
312             if (beginTarget != null && endTarget != null) {
313                 float bias = 0.5f;
314                 if (orientation == ConstraintWidget.HORIZONTAL) {
315                     bias = head.mHorizontalBiasPercent;
316                 } else {
317                     bias = head.mVerticalBiasPercent;
318                 }
319                 int beginMargin = begin.getMargin();
320                 int endMargin = end.getMargin();
321                 system.addCentering(begin.mSolverVariable, beginTarget,
322                         beginMargin, bias, endTarget, end.mSolverVariable,
323                         endMargin, SolverVariable.STRENGTH_CENTERING);
324             }
325         } else if (isChainSpread && firstVisibleWidget != null) {
326             // for chain spread, we need to add equal dimensions in between *visible* widgets
327             widget = firstVisibleWidget;
328             ConstraintWidget previousVisibleWidget = firstVisibleWidget;
329             boolean applyFixedEquality = chainHead.mWidgetsMatchCount > 0
330                     && (chainHead.mWidgetsCount == chainHead.mWidgetsMatchCount);
331             while (widget != null) {
332                 next = widget.mNextChainWidget[orientation];
333                 while (next != null && next.getVisibility() == GONE) {
334                     next = next.mNextChainWidget[orientation];
335                 }
336                 if (next != null || widget == lastVisibleWidget) {
337                     ConstraintAnchor beginAnchor = widget.mListAnchors[offset];
338                     SolverVariable begin = beginAnchor.mSolverVariable;
339                     SolverVariable beginTarget = beginAnchor.mTarget != null
340                             ? beginAnchor.mTarget.mSolverVariable : null;
341                     if (previousVisibleWidget != widget) {
342                         beginTarget =
343                                 previousVisibleWidget.mListAnchors[offset + 1].mSolverVariable;
344                     } else if (widget == firstVisibleWidget) {
345                         beginTarget = first.mListAnchors[offset].mTarget != null
346                                 ? first.mListAnchors[offset].mTarget.mSolverVariable : null;
347                     }
348 
349                     ConstraintAnchor beginNextAnchor = null;
350                     SolverVariable beginNext = null;
351                     @SuppressWarnings("unused") SolverVariable beginNextTarget = null;
352                     int beginMargin = beginAnchor.getMargin();
353                     int nextMargin = widget.mListAnchors[offset + 1].getMargin();
354 
355                     if (next != null) {
356                         beginNextAnchor = next.mListAnchors[offset];
357                         beginNext = beginNextAnchor.mSolverVariable;
358                     } else {
359                         beginNextAnchor = last.mListAnchors[offset + 1].mTarget;
360                         if (beginNextAnchor != null) {
361                             beginNext = beginNextAnchor.mSolverVariable;
362                         }
363                     }
364                     beginNextTarget = widget.mListAnchors[offset + 1].mSolverVariable;
365 
366                     if (beginNextAnchor != null) {
367                         nextMargin += beginNextAnchor.getMargin();
368                     }
369                     beginMargin += previousVisibleWidget.mListAnchors[offset + 1].getMargin();
370                     if (begin != null && beginTarget != null
371                             && beginNext != null && beginNextTarget != null) {
372                         int margin1 = beginMargin;
373                         if (widget == firstVisibleWidget) {
374                             margin1 = firstVisibleWidget.mListAnchors[offset].getMargin();
375                         }
376                         int margin2 = nextMargin;
377                         if (widget == lastVisibleWidget) {
378                             margin2 = lastVisibleWidget.mListAnchors[offset + 1].getMargin();
379                         }
380                         int strength = SolverVariable.STRENGTH_EQUALITY;
381                         if (applyFixedEquality) {
382                             strength = SolverVariable.STRENGTH_FIXED;
383                         }
384                         system.addCentering(begin, beginTarget, margin1, 0.5f,
385                                 beginNext, beginNextTarget, margin2,
386                                 strength);
387                     }
388                 }
389                 if (widget.getVisibility() != GONE) {
390                     previousVisibleWidget = widget;
391                 }
392                 widget = next;
393             }
394         } else if (isChainSpreadInside && firstVisibleWidget != null) {
395             // for chain spread inside, we need to add equal dimensions in between *visible* widgets
396             widget = firstVisibleWidget;
397             ConstraintWidget previousVisibleWidget = firstVisibleWidget;
398             boolean applyFixedEquality = chainHead.mWidgetsMatchCount > 0
399                     && (chainHead.mWidgetsCount == chainHead.mWidgetsMatchCount);
400             while (widget != null) {
401                 next = widget.mNextChainWidget[orientation];
402                 while (next != null && next.getVisibility() == GONE) {
403                     next = next.mNextChainWidget[orientation];
404                 }
405                 if (widget != firstVisibleWidget && widget != lastVisibleWidget && next != null) {
406                     if (next == lastVisibleWidget) {
407                         next = null;
408                     }
409                     ConstraintAnchor beginAnchor = widget.mListAnchors[offset];
410                     SolverVariable begin = beginAnchor.mSolverVariable;
411                     @SuppressWarnings("unused") SolverVariable beginTarget =
412                             beginAnchor.mTarget != null
413                             ? beginAnchor.mTarget.mSolverVariable : null;
414                     beginTarget = previousVisibleWidget.mListAnchors[offset + 1].mSolverVariable;
415                     ConstraintAnchor beginNextAnchor = null;
416                     SolverVariable beginNext = null;
417                     SolverVariable beginNextTarget = null;
418                     int beginMargin = beginAnchor.getMargin();
419                     int nextMargin = widget.mListAnchors[offset + 1].getMargin();
420 
421                     if (next != null) {
422                         beginNextAnchor = next.mListAnchors[offset];
423                         beginNext = beginNextAnchor.mSolverVariable;
424                         beginNextTarget = beginNextAnchor.mTarget != null
425                                 ? beginNextAnchor.mTarget.mSolverVariable : null;
426                     } else {
427                         beginNextAnchor = lastVisibleWidget.mListAnchors[offset];
428                         if (beginNextAnchor != null) {
429                             beginNext = beginNextAnchor.mSolverVariable;
430                         }
431                         beginNextTarget = widget.mListAnchors[offset + 1].mSolverVariable;
432                     }
433 
434                     if (beginNextAnchor != null) {
435                         nextMargin += beginNextAnchor.getMargin();
436                     }
437                     beginMargin += previousVisibleWidget.mListAnchors[offset + 1].getMargin();
438                     int strength = SolverVariable.STRENGTH_HIGHEST;
439                     if (applyFixedEquality) {
440                         strength = SolverVariable.STRENGTH_FIXED;
441                     }
442                     if (begin != null && beginTarget != null
443                             && beginNext != null && beginNextTarget != null) {
444                         system.addCentering(begin, beginTarget, beginMargin, 0.5f,
445                                 beginNext, beginNextTarget, nextMargin,
446                                 strength);
447                     }
448                 }
449                 if (widget.getVisibility() != GONE) {
450                     previousVisibleWidget = widget;
451                 }
452                 widget = next;
453             }
454             ConstraintAnchor begin = firstVisibleWidget.mListAnchors[offset];
455             ConstraintAnchor beginTarget = first.mListAnchors[offset].mTarget;
456             ConstraintAnchor end = lastVisibleWidget.mListAnchors[offset + 1];
457             ConstraintAnchor endTarget = last.mListAnchors[offset + 1].mTarget;
458             int endPointsStrength = SolverVariable.STRENGTH_EQUALITY;
459             if (beginTarget != null) {
460                 if (firstVisibleWidget != lastVisibleWidget) {
461                     system.addEquality(begin.mSolverVariable, beginTarget.mSolverVariable,
462                             begin.getMargin(), endPointsStrength);
463                 } else if (endTarget != null) {
464                     system.addCentering(begin.mSolverVariable, beginTarget.mSolverVariable,
465                             begin.getMargin(), 0.5f, end.mSolverVariable, endTarget.mSolverVariable,
466                             end.getMargin(), endPointsStrength);
467                 }
468             }
469             if (endTarget != null && (firstVisibleWidget != lastVisibleWidget)) {
470                 system.addEquality(end.mSolverVariable,
471                         endTarget.mSolverVariable, -end.getMargin(), endPointsStrength);
472             }
473 
474         }
475 
476         // final centering, necessary if the chain is larger than the available space...
477         if ((isChainSpread || isChainSpreadInside) && firstVisibleWidget
478                 != null && firstVisibleWidget != lastVisibleWidget) {
479             ConstraintAnchor begin = firstVisibleWidget.mListAnchors[offset];
480             if (lastVisibleWidget == null) {
481                 lastVisibleWidget = firstVisibleWidget;
482             }
483             ConstraintAnchor end = lastVisibleWidget.mListAnchors[offset + 1];
484             SolverVariable beginTarget =
485                     begin.mTarget != null ? begin.mTarget.mSolverVariable : null;
486             SolverVariable endTarget = end.mTarget != null ? end.mTarget.mSolverVariable : null;
487             if (last != lastVisibleWidget) {
488                 ConstraintAnchor realEnd = last.mListAnchors[offset + 1];
489                 endTarget = realEnd.mTarget != null ? realEnd.mTarget.mSolverVariable : null;
490             }
491             if (firstVisibleWidget == lastVisibleWidget) {
492                 begin = firstVisibleWidget.mListAnchors[offset];
493                 end = firstVisibleWidget.mListAnchors[offset + 1];
494             }
495             if (beginTarget != null && endTarget != null) {
496                 float bias = 0.5f;
497                 int beginMargin = begin.getMargin();
498                 int endMargin = lastVisibleWidget.mListAnchors[offset + 1].getMargin();
499                 system.addCentering(begin.mSolverVariable, beginTarget, beginMargin,
500                         bias, endTarget, end.mSolverVariable, endMargin,
501                         SolverVariable.STRENGTH_EQUALITY);
502             }
503         }
504     }
505 }
506