1 /*
2  * Copyright (C) 2019 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.analyzer;
18 
19 import static androidx.constraintlayout.core.widgets.ConstraintWidget.HORIZONTAL;
20 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_PERCENT;
21 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_RATIO;
22 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_SPREAD;
23 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_WRAP;
24 import static androidx.constraintlayout.core.widgets.ConstraintWidget.VERTICAL;
25 
26 import androidx.constraintlayout.core.widgets.ConstraintAnchor;
27 import androidx.constraintlayout.core.widgets.ConstraintWidget;
28 
29 public abstract class WidgetRun implements Dependency {
30     public int matchConstraintsType;
31     ConstraintWidget mWidget;
32     RunGroup mRunGroup;
33     protected ConstraintWidget.DimensionBehaviour mDimensionBehavior;
34     DimensionDependency mDimension = new DimensionDependency(this);
35 
36     public int orientation = HORIZONTAL;
37     boolean mResolved = false;
38     public DependencyNode start = new DependencyNode(this);
39     public DependencyNode end = new DependencyNode(this);
40 
41     @SuppressWarnings("HiddenTypeParameter")
42     protected RunType mRunType = RunType.NONE;
43 
WidgetRun(ConstraintWidget widget)44     public WidgetRun(ConstraintWidget widget) {
45         this.mWidget = widget;
46     }
47 
48     @SuppressWarnings("HiddenAbstractMethod")
clear()49     abstract void clear();
50 
51     @SuppressWarnings("HiddenAbstractMethod")
apply()52     abstract void apply();
53 
54     @SuppressWarnings("HiddenAbstractMethod")
applyToWidget()55     abstract void applyToWidget();
56 
57     @SuppressWarnings("HiddenAbstractMethod")
reset()58     abstract void reset();
59 
60     @SuppressWarnings("HiddenAbstractMethod")
supportsWrapComputation()61     abstract boolean supportsWrapComputation();
62 
isDimensionResolved()63     public boolean isDimensionResolved() {
64         return mDimension.resolved;
65     }
66 
67     // @TODO: add description
isCenterConnection()68     public boolean isCenterConnection() {
69         int connections = 0;
70         int count = start.mTargets.size();
71         for (int i = 0; i < count; i++) {
72             DependencyNode dependency = start.mTargets.get(i);
73             if (dependency.mRun != this) {
74                 connections++;
75             }
76         }
77         count = end.mTargets.size();
78         for (int i = 0; i < count; i++) {
79             DependencyNode dependency = end.mTargets.get(i);
80             if (dependency.mRun != this) {
81                 connections++;
82             }
83         }
84         return connections >= 2;
85     }
86 
87     // @TODO: add description
wrapSize(int direction)88     public long wrapSize(int direction) {
89         if (mDimension.resolved) {
90             long size = mDimension.value;
91             if (isCenterConnection()) { //start.targets.size() > 0 && end.targets.size() > 0) {
92                 size += start.mMargin - end.mMargin;
93             } else {
94                 if (direction == RunGroup.START) {
95                     size += start.mMargin;
96                 } else {
97                     size -= end.mMargin;
98                 }
99             }
100             return size;
101         }
102         return 0;
103     }
104 
getTarget(ConstraintAnchor anchor)105     protected final DependencyNode getTarget(ConstraintAnchor anchor) {
106         if (anchor.mTarget == null) {
107             return null;
108         }
109         DependencyNode target = null;
110         ConstraintWidget targetWidget = anchor.mTarget.mOwner;
111         ConstraintAnchor.Type targetType = anchor.mTarget.mType;
112         switch (targetType) {
113             case LEFT: {
114                 HorizontalWidgetRun run = targetWidget.mHorizontalRun;
115                 target = run.start;
116             }
117             break;
118             case RIGHT: {
119                 HorizontalWidgetRun run = targetWidget.mHorizontalRun;
120                 target = run.end;
121             }
122             break;
123             case TOP: {
124                 VerticalWidgetRun run = targetWidget.mVerticalRun;
125                 target = run.start;
126             }
127             break;
128             case BASELINE: {
129                 VerticalWidgetRun run = targetWidget.mVerticalRun;
130                 target = run.baseline;
131             }
132             break;
133             case BOTTOM: {
134                 VerticalWidgetRun run = targetWidget.mVerticalRun;
135                 target = run.end;
136             }
137             break;
138             default:
139                 break;
140         }
141         return target;
142     }
143 
updateRunCenter(Dependency dependency, ConstraintAnchor startAnchor, ConstraintAnchor endAnchor, int orientation)144     protected void updateRunCenter(Dependency dependency,
145             ConstraintAnchor startAnchor,
146             ConstraintAnchor endAnchor,
147             int orientation) {
148         DependencyNode startTarget = getTarget(startAnchor);
149         DependencyNode endTarget = getTarget(endAnchor);
150 
151         if (!(startTarget.resolved && endTarget.resolved)) {
152             return;
153         }
154 
155         int startPos = startTarget.value + startAnchor.getMargin();
156         int endPos = endTarget.value - endAnchor.getMargin();
157         int distance = endPos - startPos;
158 
159         if (!mDimension.resolved
160                 && mDimensionBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT) {
161             resolveDimension(orientation, distance);
162         }
163 
164         if (!mDimension.resolved) {
165             return;
166         }
167 
168         if (mDimension.value == distance) {
169             start.resolve(startPos);
170             end.resolve(endPos);
171             return;
172         }
173 
174         // Otherwise, we have to center
175         float bias = orientation == HORIZONTAL ? mWidget.getHorizontalBiasPercent()
176                 : mWidget.getVerticalBiasPercent();
177 
178         if (startTarget == endTarget) {
179             startPos = startTarget.value;
180             endPos = endTarget.value;
181             // TODO: taking advantage of bias here would be a nice feature to support,
182             // but for now let's stay compatible with 1.1
183             bias = 0.5f;
184         }
185 
186         int availableDistance = (endPos - startPos - mDimension.value);
187         start.resolve((int) (0.5f + startPos + availableDistance * bias));
188         end.resolve(start.value + mDimension.value);
189     }
190 
resolveDimension(int orientation, int distance)191     private void resolveDimension(int orientation, int distance) {
192         switch (matchConstraintsType) {
193             case MATCH_CONSTRAINT_SPREAD: {
194                 mDimension.resolve(getLimitedDimension(distance, orientation));
195             }
196             break;
197             case MATCH_CONSTRAINT_PERCENT: {
198                 ConstraintWidget parent = mWidget.getParent();
199                 if (parent != null) {
200                     WidgetRun run = orientation == HORIZONTAL
201                             ? parent.mHorizontalRun
202                             : parent.mVerticalRun;
203                     if (run.mDimension.resolved) {
204                         float percent = orientation == HORIZONTAL
205                                 ? mWidget.mMatchConstraintPercentWidth
206                                 : mWidget.mMatchConstraintPercentHeight;
207                         int targetDimensionValue = run.mDimension.value;
208                         int size = (int) (0.5f + targetDimensionValue * percent);
209                         mDimension.resolve(getLimitedDimension(size, orientation));
210                     }
211                 }
212             }
213             break;
214             case MATCH_CONSTRAINT_WRAP: {
215                 int wrapValue = getLimitedDimension(mDimension.wrapValue, orientation);
216                 mDimension.resolve(Math.min(wrapValue, distance));
217             }
218             break;
219             case MATCH_CONSTRAINT_RATIO: {
220                 if (mWidget.mHorizontalRun.mDimensionBehavior
221                         == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT
222                         && mWidget.mHorizontalRun.matchConstraintsType == MATCH_CONSTRAINT_RATIO
223                         && mWidget.mVerticalRun.mDimensionBehavior
224                         == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT
225                         && mWidget.mVerticalRun.matchConstraintsType == MATCH_CONSTRAINT_RATIO) {
226                     // pof
227                 } else {
228                     WidgetRun run = (orientation == HORIZONTAL)
229                             ? mWidget.mVerticalRun : mWidget.mHorizontalRun;
230                     if (run.mDimension.resolved) {
231                         float ratio = mWidget.getDimensionRatio();
232                         int value;
233                         if (orientation == VERTICAL) {
234                             value = (int) (0.5f + run.mDimension.value / ratio);
235                         } else {
236                             value = (int) (0.5f + ratio * run.mDimension.value);
237                         }
238                         mDimension.resolve(value);
239                     }
240                 }
241             }
242             break;
243             default:
244                 break;
245         }
246     }
247 
updateRunStart(Dependency dependency)248     protected void updateRunStart(Dependency dependency) {
249 
250     }
251 
updateRunEnd(Dependency dependency)252     protected void updateRunEnd(Dependency dependency) {
253 
254     }
255 
256     // @TODO: add description
257     @Override
update(Dependency dependency)258     public void update(Dependency dependency) {
259     }
260 
getLimitedDimension(int dimension, int orientation)261     protected final int getLimitedDimension(int dimension, int orientation) {
262         if (orientation == HORIZONTAL) {
263             int max = mWidget.mMatchConstraintMaxWidth;
264             int min = mWidget.mMatchConstraintMinWidth;
265             int value = Math.max(min, dimension);
266             if (max > 0) {
267                 value = Math.min(max, dimension);
268             }
269             if (value != dimension) {
270                 dimension = value;
271             }
272         } else {
273             int max = mWidget.mMatchConstraintMaxHeight;
274             int min = mWidget.mMatchConstraintMinHeight;
275             int value = Math.max(min, dimension);
276             if (max > 0) {
277                 value = Math.min(max, dimension);
278             }
279             if (value != dimension) {
280                 dimension = value;
281             }
282         }
283         return dimension;
284     }
285 
getTarget(ConstraintAnchor anchor, int orientation)286     protected final DependencyNode getTarget(ConstraintAnchor anchor, int orientation) {
287         if (anchor.mTarget == null) {
288             return null;
289         }
290         DependencyNode target = null;
291         ConstraintWidget targetWidget = anchor.mTarget.mOwner;
292         WidgetRun run = (orientation == ConstraintWidget.HORIZONTAL)
293                 ? targetWidget.mHorizontalRun : targetWidget.mVerticalRun;
294         ConstraintAnchor.Type targetType = anchor.mTarget.mType;
295         switch (targetType) {
296             case TOP:
297             case LEFT: {
298                 target = run.start;
299             }
300             break;
301             case BOTTOM:
302             case RIGHT: {
303                 target = run.end;
304             }
305             break;
306             default:
307                 break;
308         }
309         return target;
310     }
311 
addTarget(DependencyNode node, DependencyNode target, int margin)312     protected final void addTarget(DependencyNode node,
313             DependencyNode target,
314             int margin) {
315         node.mTargets.add(target);
316         node.mMargin = margin;
317         target.mDependencies.add(node);
318     }
319 
addTarget(DependencyNode node, DependencyNode target, int marginFactor, @SuppressWarnings("HiddenTypeParameter") DimensionDependency dimensionDependency)320     protected final void addTarget(DependencyNode node,
321             DependencyNode target,
322             int marginFactor,
323             @SuppressWarnings("HiddenTypeParameter") DimensionDependency
324                     dimensionDependency) {
325         node.mTargets.add(target);
326         node.mTargets.add(mDimension);
327         node.mMarginFactor = marginFactor;
328         node.mMarginDependency = dimensionDependency;
329         target.mDependencies.add(node);
330         dimensionDependency.mDependencies.add(node);
331     }
332 
333     // @TODO: add description
getWrapDimension()334     public long getWrapDimension() {
335         if (mDimension.resolved) {
336             return mDimension.value;
337         }
338         return 0;
339     }
340 
isResolved()341     public boolean isResolved() {
342         return mResolved;
343     }
344 
345     enum RunType {NONE, START, END, CENTER}
346 }
347