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.HORIZONTAL;
20 import static androidx.constraintlayout.core.widgets.ConstraintWidget.VERTICAL;
21 
22 import androidx.constraintlayout.core.LinearSystem;
23 import androidx.constraintlayout.core.widgets.Chain;
24 import androidx.constraintlayout.core.widgets.ConstraintWidget;
25 import androidx.constraintlayout.core.widgets.ConstraintWidgetContainer;
26 
27 import java.lang.ref.WeakReference;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 
31 /**
32  * Represents a group of widget for the grouping mechanism.
33  */
34 public class WidgetGroup {
35     private static final boolean DEBUG = false;
36     ArrayList<ConstraintWidget> mWidgets = new ArrayList<>();
37     static int sCount = 0;
38     int mId = -1;
39     boolean mAuthoritative = false;
40     int mOrientation = HORIZONTAL;
41     ArrayList<MeasureResult> mResults = null;
42     private int mMoveTo = -1;
43 
WidgetGroup(int orientation)44     public WidgetGroup(int orientation) {
45         mId = sCount++;
46         this.mOrientation = orientation;
47     }
48 
getOrientation()49     public int getOrientation() {
50         return mOrientation;
51     }
52 
getId()53     public int getId() {
54         return mId;
55     }
56 
57     // @TODO: add description
add(ConstraintWidget widget)58     public boolean add(ConstraintWidget widget) {
59         if (mWidgets.contains(widget)) {
60             return false;
61         }
62         mWidgets.add(widget);
63         return true;
64     }
65 
setAuthoritative(boolean isAuthoritative)66     public void setAuthoritative(boolean isAuthoritative) {
67         mAuthoritative = isAuthoritative;
68     }
69 
isAuthoritative()70     public boolean isAuthoritative() {
71         return mAuthoritative;
72     }
73 
getOrientationString()74     private String getOrientationString() {
75         if (mOrientation == HORIZONTAL) {
76             return "Horizontal";
77         } else if (mOrientation == VERTICAL) {
78             return "Vertical";
79         } else if (mOrientation == BOTH) {
80             return "Both";
81         }
82         return "Unknown";
83     }
84 
85     @Override
toString()86     public String toString() {
87         String ret = getOrientationString() + " [" + mId + "] <";
88         for (ConstraintWidget widget : mWidgets) {
89             ret += " " + widget.getDebugName();
90         }
91         ret += " >";
92         return ret;
93     }
94 
95     // @TODO: add description
moveTo(int orientation, WidgetGroup widgetGroup)96     public void moveTo(int orientation, WidgetGroup widgetGroup) {
97         if (DEBUG) {
98             System.out.println("Move all widgets (" + this + ") from "
99                     + mId + " to " + widgetGroup.getId() + "(" + widgetGroup + ")");
100             System.out.println("" +
101                     "do not call  "+ measureWrap(  orientation, new ConstraintWidget()));
102         }
103         for (ConstraintWidget widget : mWidgets) {
104             widgetGroup.add(widget);
105             if (orientation == HORIZONTAL) {
106                 widget.horizontalGroup = widgetGroup.getId();
107             } else {
108                 widget.verticalGroup = widgetGroup.getId();
109             }
110         }
111         mMoveTo = widgetGroup.mId;
112     }
113 
114     // @TODO: add description
clear()115     public void clear() {
116         mWidgets.clear();
117     }
118 
measureWrap(int orientation, ConstraintWidget widget)119     private int measureWrap(int orientation, ConstraintWidget widget) {
120         ConstraintWidget.DimensionBehaviour behaviour = widget.getDimensionBehaviour(orientation);
121         if (behaviour == ConstraintWidget.DimensionBehaviour.WRAP_CONTENT
122                 || behaviour == ConstraintWidget.DimensionBehaviour.MATCH_PARENT
123                 || behaviour == ConstraintWidget.DimensionBehaviour.FIXED) {
124             int dimension;
125             if (orientation == HORIZONTAL) {
126                 dimension = widget.getWidth();
127             } else {
128                 dimension = widget.getHeight();
129             }
130             return dimension;
131         }
132         return -1;
133     }
134 
135     // @TODO: add description
measureWrap(LinearSystem system, int orientation)136     public int measureWrap(LinearSystem system, int orientation) {
137         int count = mWidgets.size();
138         if (count == 0) {
139             return 0;
140         }
141         // TODO: add direct wrap computation for simpler cases instead of calling the solver
142         return solverMeasure(system, mWidgets, orientation);
143     }
144 
solverMeasure(LinearSystem system, ArrayList<ConstraintWidget> widgets, int orientation)145     private int solverMeasure(LinearSystem system,
146             ArrayList<ConstraintWidget> widgets,
147             int orientation) {
148         ConstraintWidgetContainer container =
149                 (ConstraintWidgetContainer) widgets.get(0).getParent();
150         system.reset();
151         @SuppressWarnings("unused") boolean prevDebug = LinearSystem.FULL_DEBUG;
152         container.addToSolver(system, false);
153         for (int i = 0; i < widgets.size(); i++) {
154             ConstraintWidget widget = widgets.get(i);
155             widget.addToSolver(system, false);
156         }
157         if (orientation == HORIZONTAL) {
158             if (container.mHorizontalChainsSize > 0) {
159                 Chain.applyChainConstraints(container, system, widgets, HORIZONTAL);
160             }
161         }
162         if (orientation == VERTICAL) {
163             if (container.mVerticalChainsSize > 0) {
164                 Chain.applyChainConstraints(container, system, widgets, VERTICAL);
165             }
166         }
167 
168         try {
169             system.minimize();
170         } catch (Exception e) {
171             //TODO remove fancy version of e.printStackTrace()
172             System.err.println(e.toString()+"\n"+Arrays.toString(e.getStackTrace())
173                     .replace("[","   at ")
174                     .replace(",","\n   at")
175                     .replace("]",""));
176         }
177 
178         // save results
179         mResults = new ArrayList<>();
180         for (int i = 0; i < widgets.size(); i++) {
181             ConstraintWidget widget = widgets.get(i);
182             MeasureResult result = new MeasureResult(widget, system, orientation);
183             mResults.add(result);
184         }
185 
186         if (orientation == HORIZONTAL) {
187             int left = system.getObjectVariableValue(container.mLeft);
188             int right = system.getObjectVariableValue(container.mRight);
189             system.reset();
190             return right - left;
191         } else {
192             int top = system.getObjectVariableValue(container.mTop);
193             int bottom = system.getObjectVariableValue(container.mBottom);
194             system.reset();
195             return bottom - top;
196         }
197     }
198 
setOrientation(int orientation)199     public void setOrientation(int orientation) {
200         this.mOrientation = orientation;
201     }
202 
203     // @TODO: add description
apply()204     public void apply() {
205         if (mResults == null) {
206             return;
207         }
208         if (!mAuthoritative) {
209             return;
210         }
211         for (int i = 0; i < mResults.size(); i++) {
212             MeasureResult result = mResults.get(i);
213             result.apply();
214         }
215     }
216 
217     // @TODO: add description
intersectWith(WidgetGroup group)218     public boolean intersectWith(WidgetGroup group) {
219         for (int i = 0; i < mWidgets.size(); i++) {
220             ConstraintWidget widget = mWidgets.get(i);
221             if (group.contains(widget)) {
222                 return true;
223             }
224         }
225         return false;
226     }
227 
contains(ConstraintWidget widget)228     private boolean contains(ConstraintWidget widget) {
229         return mWidgets.contains(widget);
230     }
231 
232     // @TODO: add description
size()233     public int size() {
234         return mWidgets.size();
235     }
236 
237     // @TODO: add description
cleanup(ArrayList<WidgetGroup> dependencyLists)238     public void cleanup(ArrayList<WidgetGroup> dependencyLists) {
239         final int count = mWidgets.size();
240         if (mMoveTo != -1 && count > 0) {
241             for (int i = 0; i < dependencyLists.size(); i++) {
242                 WidgetGroup group = dependencyLists.get(i);
243                 if (mMoveTo == group.mId) {
244                     moveTo(mOrientation, group);
245                 }
246             }
247         }
248         if (count == 0) {
249             dependencyLists.remove(this);
250             return;
251         }
252     }
253 
254 
255     static class MeasureResult {
256         WeakReference<ConstraintWidget> mWidgetRef;
257         int mLeft;
258         int mTop;
259         int mRight;
260         int mBottom;
261         int mBaseline;
262         int mOrientation;
263 
MeasureResult(ConstraintWidget widget, LinearSystem system, int orientation)264         MeasureResult(ConstraintWidget widget, LinearSystem system, int orientation) {
265             mWidgetRef = new WeakReference<>(widget);
266             mLeft = system.getObjectVariableValue(widget.mLeft);
267             mTop = system.getObjectVariableValue(widget.mTop);
268             mRight = system.getObjectVariableValue(widget.mRight);
269             mBottom = system.getObjectVariableValue(widget.mBottom);
270             mBaseline = system.getObjectVariableValue(widget.mBaseline);
271             this.mOrientation = orientation;
272         }
273 
apply()274         public void apply() {
275             ConstraintWidget widget = mWidgetRef.get();
276             if (widget != null) {
277                 widget.setFinalFrame(mLeft, mTop, mRight, mBottom, mBaseline, mOrientation);
278             }
279         }
280     }
281 }
282