1 /*
2  * Copyright (C) 2020 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 package androidx.constraintlayout.core.widgets.analyzer;
17 
18 import static androidx.constraintlayout.core.widgets.ConstraintWidget.BOTH;
19 import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.FIXED;
20 import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.MATCH_PARENT;
21 import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.WRAP_CONTENT;
22 import static androidx.constraintlayout.core.widgets.ConstraintWidget.HORIZONTAL;
23 import static androidx.constraintlayout.core.widgets.ConstraintWidget.VERTICAL;
24 
25 import androidx.constraintlayout.core.widgets.Barrier;
26 import androidx.constraintlayout.core.widgets.ConstraintAnchor;
27 import androidx.constraintlayout.core.widgets.ConstraintWidget;
28 import androidx.constraintlayout.core.widgets.ConstraintWidgetContainer;
29 import androidx.constraintlayout.core.widgets.Flow;
30 import androidx.constraintlayout.core.widgets.Guideline;
31 import androidx.constraintlayout.core.widgets.HelperWidget;
32 
33 import java.util.ArrayList;
34 
35 /**
36  * Implements a simple grouping mechanism, to group interdependent widgets together.
37  *
38  * TODO: we should move towards a more leaner implementation
39  *          -- this is more expensive as it could be.
40  */
41 public class Grouping {
42 
43     private static final boolean DEBUG = false;
44     private static final boolean DEBUG_GROUPING = false;
45     private static final boolean FORCE_USE = true;
46 
47     // @TODO: add description
validInGroup(ConstraintWidget.DimensionBehaviour layoutHorizontal, ConstraintWidget.DimensionBehaviour layoutVertical, ConstraintWidget.DimensionBehaviour widgetHorizontal, ConstraintWidget.DimensionBehaviour widgetVertical)48     public static boolean validInGroup(ConstraintWidget.DimensionBehaviour layoutHorizontal,
49             ConstraintWidget.DimensionBehaviour layoutVertical,
50             ConstraintWidget.DimensionBehaviour widgetHorizontal,
51             ConstraintWidget.DimensionBehaviour widgetVertical) {
52         boolean fixedHorizontal = widgetHorizontal == FIXED || widgetHorizontal == WRAP_CONTENT
53                 || (widgetHorizontal == MATCH_PARENT && layoutHorizontal != WRAP_CONTENT);
54         boolean fixedVertical = widgetVertical == FIXED || widgetVertical == WRAP_CONTENT
55                 || (widgetVertical == MATCH_PARENT && layoutVertical != WRAP_CONTENT);
56         if (fixedHorizontal || fixedVertical) {
57             return true;
58         }
59         return false;
60     }
61 
62     // @TODO: add description
simpleSolvingPass(ConstraintWidgetContainer layout, BasicMeasure.Measurer measurer)63     public static boolean simpleSolvingPass(ConstraintWidgetContainer layout,
64             BasicMeasure.Measurer measurer) {
65 
66         if (DEBUG) {
67             System.out.println("*** GROUP SOLVING ***");
68         }
69         ArrayList<ConstraintWidget> children = layout.getChildren();
70 
71         final int count = children.size();
72 
73         ArrayList<Guideline> verticalGuidelines = null;
74         ArrayList<Guideline> horizontalGuidelines = null;
75         ArrayList<HelperWidget> horizontalBarriers = null;
76         ArrayList<HelperWidget> verticalBarriers = null;
77         ArrayList<ConstraintWidget> isolatedHorizontalChildren = null;
78         ArrayList<ConstraintWidget> isolatedVerticalChildren = null;
79 
80         for (int i = 0; i < count; i++) {
81             ConstraintWidget child = children.get(i);
82             if (!validInGroup(layout.getHorizontalDimensionBehaviour(),
83                     layout.getVerticalDimensionBehaviour(),
84                     child.getHorizontalDimensionBehaviour(),
85                     child.getVerticalDimensionBehaviour())) {
86                 if (DEBUG) {
87                     System.out.println("*** NO GROUP SOLVING ***");
88                 }
89                 return false;
90             }
91             if (child instanceof Flow) {
92                 return false;
93             }
94         }
95         if (layout.mMetrics != null) {
96             layout.mMetrics.grouping++;
97         }
98         for (int i = 0; i < count; i++) {
99             ConstraintWidget child = children.get(i);
100             if (!validInGroup(layout.getHorizontalDimensionBehaviour(),
101                     layout.getVerticalDimensionBehaviour(),
102                     child.getHorizontalDimensionBehaviour(),
103                     child.getVerticalDimensionBehaviour())) {
104                 ConstraintWidgetContainer.measure(0, child, measurer,
105                         layout.mMeasure, BasicMeasure.Measure.SELF_DIMENSIONS);
106             }
107             if (child instanceof Guideline) {
108                 Guideline guideline = (Guideline) child;
109                 if (guideline.getOrientation() == HORIZONTAL) {
110                     if (horizontalGuidelines == null) {
111                         horizontalGuidelines = new ArrayList<>();
112                     }
113                     horizontalGuidelines.add(guideline);
114                 }
115                 if (guideline.getOrientation() == VERTICAL) {
116                     if (verticalGuidelines == null) {
117                         verticalGuidelines = new ArrayList<>();
118                     }
119                     verticalGuidelines.add(guideline);
120                 }
121             }
122             if (child instanceof HelperWidget) {
123                 if (child instanceof Barrier) {
124                     Barrier barrier = (Barrier) child;
125                     if (barrier.getOrientation() == HORIZONTAL) {
126                         if (horizontalBarriers == null) {
127                             horizontalBarriers = new ArrayList<>();
128                         }
129                         horizontalBarriers.add(barrier);
130                     }
131                     if (barrier.getOrientation() == VERTICAL) {
132                         if (verticalBarriers == null) {
133                             verticalBarriers = new ArrayList<>();
134                         }
135                         verticalBarriers.add(barrier);
136                     }
137                 } else {
138                     HelperWidget helper = (HelperWidget) child;
139                     if (horizontalBarriers == null) {
140                         horizontalBarriers = new ArrayList<>();
141                     }
142                     horizontalBarriers.add(helper);
143                     if (verticalBarriers == null) {
144                         verticalBarriers = new ArrayList<>();
145                     }
146                     verticalBarriers.add(helper);
147                 }
148             }
149             if (child.mLeft.mTarget == null && child.mRight.mTarget == null
150                     && !(child instanceof Guideline) && !(child instanceof Barrier)) {
151                 if (isolatedHorizontalChildren == null) {
152                     isolatedHorizontalChildren = new ArrayList<>();
153                 }
154                 isolatedHorizontalChildren.add(child);
155             }
156             if (child.mTop.mTarget == null && child.mBottom.mTarget == null
157                     && child.mBaseline.mTarget == null
158                     && !(child instanceof Guideline) && !(child instanceof Barrier)) {
159                 if (isolatedVerticalChildren == null) {
160                     isolatedVerticalChildren = new ArrayList<>();
161                 }
162                 isolatedVerticalChildren.add(child);
163             }
164         }
165         ArrayList<WidgetGroup> allDependencyLists = new ArrayList<>();
166 
167         if (FORCE_USE || layout.getHorizontalDimensionBehaviour()
168                 == ConstraintWidget.DimensionBehaviour.WRAP_CONTENT) {
169             //horizontalDependencyLists; //new ArrayList<>();
170             ArrayList<WidgetGroup> dependencyLists = allDependencyLists;
171 
172             if (verticalGuidelines != null) {
173                 for (Guideline guideline : verticalGuidelines) {
174                     findDependents(guideline, HORIZONTAL, dependencyLists, null);
175                 }
176             }
177             if (horizontalBarriers != null) {
178                 for (HelperWidget barrier : horizontalBarriers) {
179                     WidgetGroup group = findDependents(barrier, HORIZONTAL, dependencyLists, null);
180                     barrier.addDependents(dependencyLists, HORIZONTAL, group);
181                     group.cleanup(dependencyLists);
182                 }
183             }
184 
185             ConstraintAnchor left = layout.getAnchor(ConstraintAnchor.Type.LEFT);
186             if (left.getDependents() != null) {
187                 for (ConstraintAnchor first : left.getDependents()) {
188                     findDependents(first.mOwner, ConstraintWidget.HORIZONTAL,
189                             dependencyLists, null);
190                 }
191             }
192 
193             ConstraintAnchor right = layout.getAnchor(ConstraintAnchor.Type.RIGHT);
194             if (right.getDependents() != null) {
195                 for (ConstraintAnchor first : right.getDependents()) {
196                     findDependents(first.mOwner, ConstraintWidget.HORIZONTAL,
197                             dependencyLists, null);
198                 }
199             }
200 
201             ConstraintAnchor center = layout.getAnchor(ConstraintAnchor.Type.CENTER);
202             if (center.getDependents() != null) {
203                 for (ConstraintAnchor first : center.getDependents()) {
204                     findDependents(first.mOwner, ConstraintWidget.HORIZONTAL,
205                             dependencyLists, null);
206                 }
207             }
208 
209             if (isolatedHorizontalChildren != null) {
210                 for (ConstraintWidget widget : isolatedHorizontalChildren) {
211                     findDependents(widget, HORIZONTAL, dependencyLists, null);
212                 }
213             }
214         }
215 
216         if (FORCE_USE || layout.getVerticalDimensionBehaviour()
217                 == ConstraintWidget.DimensionBehaviour.WRAP_CONTENT) {
218             //verticalDependencyLists; //new ArrayList<>();
219             ArrayList<WidgetGroup> dependencyLists = allDependencyLists;
220 
221             if (horizontalGuidelines != null) {
222                 for (Guideline guideline : horizontalGuidelines) {
223                     findDependents(guideline, VERTICAL, dependencyLists, null);
224                 }
225             }
226             if (verticalBarriers != null) {
227                 for (HelperWidget barrier : verticalBarriers) {
228                     WidgetGroup group = findDependents(barrier, VERTICAL, dependencyLists, null);
229                     barrier.addDependents(dependencyLists, VERTICAL, group);
230                     group.cleanup(dependencyLists);
231                 }
232             }
233 
234             ConstraintAnchor top = layout.getAnchor(ConstraintAnchor.Type.TOP);
235             if (top.getDependents() != null) {
236                 for (ConstraintAnchor first : top.getDependents()) {
237                     findDependents(first.mOwner, VERTICAL, dependencyLists, null);
238                 }
239             }
240 
241             ConstraintAnchor baseline = layout.getAnchor(ConstraintAnchor.Type.BASELINE);
242             if (baseline.getDependents() != null) {
243                 for (ConstraintAnchor first : baseline.getDependents()) {
244                     findDependents(first.mOwner, VERTICAL, dependencyLists, null);
245                 }
246             }
247 
248             ConstraintAnchor bottom = layout.getAnchor(ConstraintAnchor.Type.BOTTOM);
249             if (bottom.getDependents() != null) {
250                 for (ConstraintAnchor first : bottom.getDependents()) {
251                     findDependents(first.mOwner, VERTICAL, dependencyLists, null);
252                 }
253             }
254 
255             ConstraintAnchor center = layout.getAnchor(ConstraintAnchor.Type.CENTER);
256             if (center.getDependents() != null) {
257                 for (ConstraintAnchor first : center.getDependents()) {
258                     findDependents(first.mOwner, VERTICAL, dependencyLists, null);
259                 }
260             }
261 
262             if (isolatedVerticalChildren != null) {
263                 for (ConstraintWidget widget : isolatedVerticalChildren) {
264                     findDependents(widget, VERTICAL, dependencyLists, null);
265                 }
266             }
267         }
268         // Now we may have to merge horizontal/vertical dependencies
269         for (int i = 0; i < count; i++) {
270             ConstraintWidget child = children.get(i);
271             if (child.oppositeDimensionsTied()) {
272                 WidgetGroup horizontalGroup = findGroup(allDependencyLists, child.horizontalGroup);
273                 WidgetGroup verticalGroup = findGroup(allDependencyLists, child.verticalGroup);
274                 if (horizontalGroup != null && verticalGroup != null) {
275                     if (DEBUG_GROUPING) {
276                         System.out.println("Merging " + horizontalGroup
277                                 + " to " + verticalGroup + " for " + child);
278                     }
279                     horizontalGroup.moveTo(HORIZONTAL, verticalGroup);
280                     verticalGroup.setOrientation(BOTH);
281                     allDependencyLists.remove(horizontalGroup);
282                 }
283             }
284             if (DEBUG_GROUPING) {
285                 System.out.println("Widget " + child + " => "
286                         + child.horizontalGroup + " : " + child.verticalGroup);
287             }
288         }
289 
290         if (allDependencyLists.size() <= 1) {
291             return false;
292         }
293 
294         if (DEBUG) {
295             System.out.println("----------------------------------");
296             System.out.println("-- Horizontal dependency lists:");
297             System.out.println("----------------------------------");
298             for (WidgetGroup list : allDependencyLists) {
299                 if (list.getOrientation() != VERTICAL) {
300                     System.out.println("list: " + list);
301                 }
302             }
303             System.out.println("----------------------------------");
304             System.out.println("-- Vertical dependency lists:");
305             System.out.println("----------------------------------");
306             for (WidgetGroup list : allDependencyLists) {
307                 if (list.getOrientation() != HORIZONTAL) {
308                     System.out.println("list: " + list);
309                 }
310             }
311             System.out.println("----------------------------------");
312         }
313 
314         WidgetGroup horizontalPick = null;
315         WidgetGroup verticalPick = null;
316 
317         if (layout.getHorizontalDimensionBehaviour()
318                 == ConstraintWidget.DimensionBehaviour.WRAP_CONTENT) {
319             int maxWrap = 0;
320             WidgetGroup picked = null;
321             for (WidgetGroup list : allDependencyLists) {
322                 if (list.getOrientation() == VERTICAL) {
323                     continue;
324                 }
325                 list.setAuthoritative(false);
326                 int wrap = list.measureWrap(layout.getSystem(), HORIZONTAL);
327                 if (wrap > maxWrap) {
328                     picked = list;
329                     maxWrap = wrap;
330                 }
331                 if (DEBUG) {
332                     System.out.println("list: " + list + " => " + wrap);
333                 }
334             }
335             if (picked != null) {
336                 if (DEBUG) {
337                     System.out.println("Horizontal MaxWrap : " + maxWrap + " with group " + picked);
338                 }
339                 layout.setHorizontalDimensionBehaviour(ConstraintWidget.DimensionBehaviour.FIXED);
340                 layout.setWidth(maxWrap);
341                 picked.setAuthoritative(true);
342                 horizontalPick = picked;
343             }
344         }
345 
346         if (layout.getVerticalDimensionBehaviour()
347                 == ConstraintWidget.DimensionBehaviour.WRAP_CONTENT) {
348             int maxWrap = 0;
349             WidgetGroup picked = null;
350             for (WidgetGroup list : allDependencyLists) {
351                 if (list.getOrientation() == HORIZONTAL) {
352                     continue;
353                 }
354                 list.setAuthoritative(false);
355                 int wrap = list.measureWrap(layout.getSystem(), VERTICAL);
356                 if (wrap > maxWrap) {
357                     picked = list;
358                     maxWrap = wrap;
359                 }
360                 if (DEBUG) {
361                     System.out.println("      " + list + " => " + wrap);
362                 }
363             }
364             if (picked != null) {
365                 if (DEBUG) {
366                     System.out.println("Vertical MaxWrap : " + maxWrap + " with group " + picked);
367                 }
368                 layout.setVerticalDimensionBehaviour(ConstraintWidget.DimensionBehaviour.FIXED);
369                 layout.setHeight(maxWrap);
370                 picked.setAuthoritative(true);
371                 verticalPick = picked;
372             }
373         }
374         return horizontalPick != null || verticalPick != null;
375     }
376 
findGroup(ArrayList<WidgetGroup> horizontalDependencyLists, int groupId)377     private static WidgetGroup findGroup(ArrayList<WidgetGroup> horizontalDependencyLists,
378             int groupId) {
379         final int count = horizontalDependencyLists.size();
380         for (int i = 0; i < count; i++) {
381             WidgetGroup group = horizontalDependencyLists.get(i);
382             if (groupId == group.getId()) {
383                 return group;
384             }
385         }
386         return null;
387     }
388 
389     // @TODO: add description
findDependents(ConstraintWidget constraintWidget, int orientation, ArrayList<WidgetGroup> list, WidgetGroup group)390     public static WidgetGroup findDependents(ConstraintWidget constraintWidget,
391             int orientation,
392             ArrayList<WidgetGroup> list,
393             WidgetGroup group) {
394         int groupId = -1;
395         if (orientation == ConstraintWidget.HORIZONTAL) {
396             groupId = constraintWidget.horizontalGroup;
397         } else {
398             groupId = constraintWidget.verticalGroup;
399         }
400         if (DEBUG_GROUPING) {
401             System.out.println("--- find " + (orientation == HORIZONTAL ? "Horiz" : "Vert")
402                     + " dependents of " + constraintWidget.getDebugName()
403                     + " group " + group + " widget group id " + groupId);
404         }
405         if (groupId != -1 && (group == null || (groupId != group.getId()))) {
406             // already in a group!
407             if (DEBUG_GROUPING) {
408                 System.out.println("widget " + constraintWidget.getDebugName()
409                         + " already in group " + groupId + " group: " + group);
410             }
411             for (int i = 0; i < list.size(); i++) {
412                 WidgetGroup widgetGroup = list.get(i);
413                 if (widgetGroup.getId() == groupId) {
414                     if (group != null) {
415                         if (DEBUG_GROUPING) {
416                             System.out.println("Move group " + group + " to " + widgetGroup);
417                         }
418                         group.moveTo(orientation, widgetGroup);
419                         list.remove(group);
420                     }
421                     group = widgetGroup;
422                     break;
423                 }
424             }
425         } else if (groupId != -1) {
426             return group;
427         }
428         if (group == null) {
429             if (constraintWidget instanceof HelperWidget) {
430                 HelperWidget helper = (HelperWidget) constraintWidget;
431                 groupId = helper.findGroupInDependents(orientation);
432                 if (groupId != -1) {
433                     for (int i = 0; i < list.size(); i++) {
434                         WidgetGroup widgetGroup = list.get(i);
435                         if (widgetGroup.getId() == groupId) {
436                             group = widgetGroup;
437                             break;
438                         }
439                     }
440                 }
441             }
442             if (group == null) {
443                 group = new WidgetGroup(orientation);
444             }
445             if (DEBUG_GROUPING) {
446                 System.out.println("Create group " + group
447                         + " for widget " + constraintWidget.getDebugName());
448             }
449             list.add(group);
450         }
451         if (group.add(constraintWidget)) {
452             if (constraintWidget instanceof Guideline) {
453                 Guideline guideline = (Guideline) constraintWidget;
454                 guideline.getAnchor().findDependents(guideline.getOrientation()
455                         == Guideline.HORIZONTAL ? VERTICAL : HORIZONTAL, list, group);
456             }
457             if (orientation == ConstraintWidget.HORIZONTAL) {
458                 constraintWidget.horizontalGroup = group.getId();
459                 if (DEBUG_GROUPING) {
460                     System.out.println("Widget " + constraintWidget.getDebugName()
461                             + " H group is " + constraintWidget.horizontalGroup);
462                 }
463                 constraintWidget.mLeft.findDependents(orientation, list, group);
464                 constraintWidget.mRight.findDependents(orientation, list, group);
465             } else {
466                 constraintWidget.verticalGroup = group.getId();
467                 if (DEBUG_GROUPING) {
468                     System.out.println("Widget " + constraintWidget.getDebugName()
469                             + " V group is " + constraintWidget.verticalGroup);
470                 }
471                 constraintWidget.mTop.findDependents(orientation, list, group);
472                 constraintWidget.mBaseline.findDependents(orientation, list, group);
473                 constraintWidget.mBottom.findDependents(orientation, list, group);
474             }
475             constraintWidget.mCenter.findDependents(orientation, list, group);
476         }
477         return group;
478     }
479 }
480