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.DimensionBehaviour.FIXED;
20 import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT;
21 import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.MATCH_PARENT;
22 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_PERCENT;
23 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_RATIO;
24 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_SPREAD;
25 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_WRAP;
26 import static androidx.constraintlayout.core.widgets.ConstraintWidget.VERTICAL;
27 import static androidx.constraintlayout.core.widgets.analyzer.WidgetRun.RunType.CENTER;
28 
29 import androidx.constraintlayout.core.widgets.ConstraintAnchor;
30 import androidx.constraintlayout.core.widgets.ConstraintWidget;
31 import androidx.constraintlayout.core.widgets.Helper;
32 
33 public class VerticalWidgetRun extends WidgetRun {
34     private static final boolean FORCE_USE = true;
35     public DependencyNode baseline = new DependencyNode(this);
36     androidx.constraintlayout.core.widgets.analyzer.DimensionDependency mBaselineDimension = null;
37 
VerticalWidgetRun(ConstraintWidget widget)38     public VerticalWidgetRun(ConstraintWidget widget) {
39         super(widget);
40         start.mType = DependencyNode.Type.TOP;
41         end.mType = DependencyNode.Type.BOTTOM;
42         baseline.mType = DependencyNode.Type.BASELINE;
43         this.orientation = VERTICAL;
44     }
45 
46     @Override
toString()47     public String toString() {
48         return "VerticalRun " + mWidget.getDebugName();
49     }
50 
51     @Override
clear()52     void clear() {
53         mRunGroup = null;
54         start.clear();
55         end.clear();
56         baseline.clear();
57         mDimension.clear();
58         mResolved = false;
59     }
60 
61     @Override
reset()62     void reset() {
63         mResolved = false;
64         start.clear();
65         start.resolved = false;
66         end.clear();
67         end.resolved = false;
68         baseline.clear();
69         baseline.resolved = false;
70         mDimension.resolved = false;
71     }
72 
73     @Override
supportsWrapComputation()74     boolean supportsWrapComputation() {
75         if (super.mDimensionBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT) {
76             if (super.mWidget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD) {
77                 return true;
78             }
79             return false;
80         }
81         return true;
82     }
83 
84     @Override
update(Dependency dependency)85     public void update(Dependency dependency) {
86         switch (mRunType) {
87             case START: {
88                 updateRunStart(dependency);
89             }
90             break;
91             case END: {
92                 updateRunEnd(dependency);
93             }
94             break;
95             case CENTER: {
96                 updateRunCenter(dependency, mWidget.mTop, mWidget.mBottom, VERTICAL);
97                 return;
98             }
99             default:
100                 break;
101         }
102         if (FORCE_USE || dependency == mDimension) {
103             if (mDimension.readyToSolve && !mDimension.resolved) {
104                 if (mDimensionBehavior == MATCH_CONSTRAINT) {
105                     switch (mWidget.mMatchConstraintDefaultHeight) {
106                         case MATCH_CONSTRAINT_RATIO: {
107                             if (mWidget.mHorizontalRun.mDimension.resolved) {
108                                 int size = 0;
109                                 int ratioSide = mWidget.getDimensionRatioSide();
110                                 switch (ratioSide) {
111                                     case ConstraintWidget.HORIZONTAL: {
112                                         size = (int) (0.5f + mWidget.mHorizontalRun.mDimension.value
113                                                 * mWidget.getDimensionRatio());
114                                     }
115                                     break;
116                                     case ConstraintWidget.VERTICAL: {
117                                         size = (int) (0.5f + mWidget.mHorizontalRun.mDimension.value
118                                                 / mWidget.getDimensionRatio());
119                                     }
120                                     break;
121                                     case ConstraintWidget.UNKNOWN: {
122                                         size = (int) (0.5f + mWidget.mHorizontalRun.mDimension.value
123                                                 / mWidget.getDimensionRatio());
124                                     }
125                                     break;
126                                     default:
127                                         break;
128                                 }
129                                 mDimension.resolve(size);
130                             }
131                         }
132                         break;
133                         case MATCH_CONSTRAINT_PERCENT: {
134                             ConstraintWidget parent = mWidget.getParent();
135                             if (parent != null) {
136                                 if (parent.mVerticalRun.mDimension.resolved) {
137                                     float percent = mWidget.mMatchConstraintPercentHeight;
138                                     int targetDimensionValue = parent.mVerticalRun.mDimension.value;
139                                     int size = (int) (0.5f + targetDimensionValue * percent);
140                                     mDimension.resolve(size);
141                                 }
142                             }
143                         }
144                         break;
145                         default:
146                             break;
147                     }
148                 }
149             }
150         }
151         if (!(start.readyToSolve && end.readyToSolve)) {
152             return;
153         }
154         if (start.resolved && end.resolved && mDimension.resolved) {
155             return;
156         }
157 
158         if (!mDimension.resolved
159                 && mDimensionBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT
160                 && mWidget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD
161                 && !mWidget.isInVerticalChain()) {
162 
163             DependencyNode startTarget = start.mTargets.get(0);
164             DependencyNode endTarget = end.mTargets.get(0);
165             int startPos = startTarget.value + start.mMargin;
166             int endPos = endTarget.value + end.mMargin;
167 
168             int distance = endPos - startPos;
169             start.resolve(startPos);
170             end.resolve(endPos);
171             mDimension.resolve(distance);
172             return;
173         }
174 
175         if (!mDimension.resolved
176                 && mDimensionBehavior == MATCH_CONSTRAINT
177                 && matchConstraintsType == MATCH_CONSTRAINT_WRAP) {
178             if (start.mTargets.size() > 0 && end.mTargets.size() > 0) {
179                 DependencyNode startTarget = start.mTargets.get(0);
180                 DependencyNode endTarget = end.mTargets.get(0);
181                 int startPos = startTarget.value + start.mMargin;
182                 int endPos = endTarget.value + end.mMargin;
183                 int availableSpace = endPos - startPos;
184                 if (availableSpace < mDimension.wrapValue) {
185                     mDimension.resolve(availableSpace);
186                 } else {
187                     mDimension.resolve(mDimension.wrapValue);
188                 }
189             }
190         }
191 
192         if (!mDimension.resolved) {
193             return;
194         }
195         // ready to solve, centering.
196         if (start.mTargets.size() > 0 && end.mTargets.size() > 0) {
197             DependencyNode startTarget = start.mTargets.get(0);
198             DependencyNode endTarget = end.mTargets.get(0);
199             int startPos = startTarget.value + start.mMargin;
200             int endPos = endTarget.value + end.mMargin;
201             float bias = mWidget.getVerticalBiasPercent();
202             if (startTarget == endTarget) {
203                 startPos = startTarget.value;
204                 endPos = endTarget.value;
205                 // TODO: this might be a nice feature to support, but I guess for now let's stay
206                 // compatible with 1.1
207                 bias = 0.5f;
208             }
209             int distance = (endPos - startPos - mDimension.value);
210             start.resolve((int) (0.5f + startPos + distance * bias));
211             end.resolve(start.value + mDimension.value);
212         }
213     }
214 
215     @Override
apply()216     void apply() {
217         if (mWidget.measured) {
218             mDimension.resolve(mWidget.getHeight());
219         }
220         if (!mDimension.resolved) {
221             super.mDimensionBehavior = mWidget.getVerticalDimensionBehaviour();
222             if (mWidget.hasBaseline()) {
223                 mBaselineDimension = new BaselineDimensionDependency(this);
224             }
225             if (super.mDimensionBehavior != ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT) {
226                 if (mDimensionBehavior == MATCH_PARENT) {
227                     ConstraintWidget parent = mWidget.getParent();
228                     if (parent != null && parent.getVerticalDimensionBehaviour() == FIXED) {
229                         int resolvedDimension = parent.getHeight()
230                                 - mWidget.mTop.getMargin() - mWidget.mBottom.getMargin();
231                         addTarget(start, parent.mVerticalRun.start, mWidget.mTop.getMargin());
232                         addTarget(end, parent.mVerticalRun.end, -mWidget.mBottom.getMargin());
233                         mDimension.resolve(resolvedDimension);
234                         return;
235                     }
236                 }
237                 if (mDimensionBehavior == FIXED) {
238                     mDimension.resolve(mWidget.getHeight());
239                 }
240             }
241         } else {
242             if (mDimensionBehavior == MATCH_PARENT) {
243                 ConstraintWidget parent = mWidget.getParent();
244                 if (parent != null && parent.getVerticalDimensionBehaviour() == FIXED) {
245                     addTarget(start, parent.mVerticalRun.start, mWidget.mTop.getMargin());
246                     addTarget(end, parent.mVerticalRun.end, -mWidget.mBottom.getMargin());
247                     return;
248                 }
249             }
250         }
251         // three basic possibilities:
252         // <-s-e->
253         // <-s-e
254         //   s-e->
255         // and a variation if the dimension is not yet known:
256         // <-s-d-e->
257         // <-s<-d<-e
258         //   s->d->e->
259 
260         if (mDimension.resolved && mWidget.measured) {
261             if (mWidget.mListAnchors[ConstraintWidget.ANCHOR_TOP].mTarget != null
262                     && mWidget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM].mTarget
263                     != null) { // <-s-e->
264                 if (mWidget.isInVerticalChain()) {
265                     start.mMargin = mWidget.mListAnchors[ConstraintWidget.ANCHOR_TOP].getMargin();
266                     end.mMargin = -mWidget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM].getMargin();
267                 } else {
268                     DependencyNode startTarget =
269                             getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_TOP]);
270                     if (startTarget != null) {
271                         addTarget(start, startTarget,
272                                 mWidget.mListAnchors[ConstraintWidget.ANCHOR_TOP].getMargin());
273                     }
274                     DependencyNode endTarget =
275                             getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM]);
276                     if (endTarget != null) {
277                         addTarget(end, endTarget,
278                                 -mWidget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM].getMargin());
279                     }
280                     start.delegateToWidgetRun = true;
281                     end.delegateToWidgetRun = true;
282                 }
283                 if (mWidget.hasBaseline()) {
284                     addTarget(baseline, start, mWidget.getBaselineDistance());
285                 }
286             } else if (mWidget.mListAnchors[ConstraintWidget.ANCHOR_TOP].mTarget != null) { // <-s-e
287                 DependencyNode target =
288                         getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_TOP]);
289                 if (target != null) {
290                     addTarget(start, target,
291                             mWidget.mListAnchors[ConstraintWidget.ANCHOR_TOP].getMargin());
292                     addTarget(end, start, mDimension.value);
293                     if (mWidget.hasBaseline()) {
294                         addTarget(baseline, start, mWidget.getBaselineDistance());
295                     }
296                 }
297             } else if (mWidget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM].mTarget
298                     != null) {   //   s-e->
299                 DependencyNode target =
300                         getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM]);
301                 if (target != null) {
302                     addTarget(end, target,
303                             -mWidget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM].getMargin());
304                     addTarget(start, end, -mDimension.value);
305                 }
306                 if (mWidget.hasBaseline()) {
307                     addTarget(baseline, start, mWidget.getBaselineDistance());
308                 }
309             } else if (mWidget.mListAnchors[ConstraintWidget.ANCHOR_BASELINE].mTarget
310                     != null) {
311                 DependencyNode target =
312                         getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_BASELINE]);
313                 if (target != null) {
314                     addTarget(baseline, target, 0);
315                     addTarget(start, baseline, -mWidget.getBaselineDistance());
316                     addTarget(end, start, mDimension.value);
317                 }
318             } else {
319                 // no connections, nothing to do.
320                 if (!(mWidget instanceof Helper) && mWidget.getParent() != null
321                         && mWidget.getAnchor(ConstraintAnchor.Type.CENTER).mTarget == null) {
322                     DependencyNode top = mWidget.getParent().mVerticalRun.start;
323                     addTarget(start, top, mWidget.getY());
324                     addTarget(end, start, mDimension.value);
325                     if (mWidget.hasBaseline()) {
326                         addTarget(baseline, start, mWidget.getBaselineDistance());
327                     }
328                 }
329             }
330         } else {
331             if (!mDimension.resolved && mDimensionBehavior == MATCH_CONSTRAINT) {
332                 switch (mWidget.mMatchConstraintDefaultHeight) {
333                     case MATCH_CONSTRAINT_RATIO: {
334                         if (!mWidget.isInVerticalChain()) {
335                             if (mWidget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_RATIO) {
336                                 // need to look into both side
337                                 // do nothing here --
338                                 //    let the HorizontalWidgetRun::update() deal with it.
339                                 break;
340                             }
341                             // we have a ratio, but we depend on the other side computation
342                             DependencyNode targetDimension = mWidget.mHorizontalRun.mDimension;
343                             mDimension.mTargets.add(targetDimension);
344                             targetDimension.mDependencies.add(mDimension);
345                             mDimension.delegateToWidgetRun = true;
346                             mDimension.mDependencies.add(start);
347                             mDimension.mDependencies.add(end);
348                         }
349                     }
350                     break;
351                     case MATCH_CONSTRAINT_PERCENT: {
352                         // we need to look up the parent dimension
353                         ConstraintWidget parent = mWidget.getParent();
354                         if (parent == null) {
355                             break;
356                         }
357                         DependencyNode targetDimension = parent.mVerticalRun.mDimension;
358                         mDimension.mTargets.add(targetDimension);
359                         targetDimension.mDependencies.add(mDimension);
360                         mDimension.delegateToWidgetRun = true;
361                         mDimension.mDependencies.add(start);
362                         mDimension.mDependencies.add(end);
363                     }
364                     break;
365                     case MATCH_CONSTRAINT_SPREAD: {
366                         // the work is done in the update()
367                     }
368                     break;
369                     default:
370                         break;
371                 }
372             } else {
373                 mDimension.addDependency(this);
374             }
375             if (mWidget.mListAnchors[ConstraintWidget.ANCHOR_TOP].mTarget != null
376                     && mWidget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM].mTarget
377                     != null) { // <-s-d-e->
378                 if (mWidget.isInVerticalChain()) {
379                     start.mMargin = mWidget.mListAnchors[ConstraintWidget.ANCHOR_TOP].getMargin();
380                     end.mMargin = -mWidget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM].getMargin();
381                 } else {
382                     DependencyNode startTarget =
383                             getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_TOP]);
384                     DependencyNode endTarget =
385                             getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM]);
386                     if (false) {
387                         if (startTarget != null) {
388                             addTarget(start, startTarget,
389                                     mWidget.mListAnchors[ConstraintWidget.ANCHOR_TOP].getMargin());
390                         }
391                         if (endTarget != null) {
392                             addTarget(end, endTarget,
393                                     -mWidget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM]
394                                             .getMargin());
395                         }
396                     } else {
397                         if (startTarget != null) {
398                             startTarget.addDependency(this);
399                         }
400                         if (endTarget != null) {
401                             endTarget.addDependency(this);
402                         }
403                     }
404                     mRunType = CENTER;
405                 }
406                 if (mWidget.hasBaseline()) {
407                     addTarget(baseline, start, 1, mBaselineDimension);
408                 }
409             } else if (mWidget.mListAnchors[ConstraintWidget.ANCHOR_TOP].mTarget
410                     != null) { // <-s<-d<-e
411                 DependencyNode target =
412                         getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_TOP]);
413                 if (target != null) {
414                     addTarget(start, target,
415                             mWidget.mListAnchors[ConstraintWidget.ANCHOR_TOP].getMargin());
416                     addTarget(end, start, 1, mDimension);
417                     if (mWidget.hasBaseline()) {
418                         addTarget(baseline, start, 1, mBaselineDimension);
419                     }
420                     if (mDimensionBehavior == MATCH_CONSTRAINT) {
421                         if (mWidget.getDimensionRatio() > 0) {
422                             if (mWidget.mHorizontalRun.mDimensionBehavior == MATCH_CONSTRAINT) {
423                                 mWidget.mHorizontalRun.mDimension.mDependencies.add(mDimension);
424                                 mDimension.mTargets.add(mWidget.mHorizontalRun.mDimension);
425                                 mDimension.updateDelegate = this;
426                             }
427                         }
428                     }
429                 }
430             } else if (mWidget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM].mTarget
431                     != null) {   //   s->d->e->
432                 DependencyNode target =
433                         getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM]);
434                 if (target != null) {
435                     addTarget(end, target,
436                             -mWidget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM].getMargin());
437                     addTarget(start, end, -1, mDimension);
438                     if (mWidget.hasBaseline()) {
439                         addTarget(baseline, start, 1, mBaselineDimension);
440                     }
441                 }
442             } else if (mWidget.mListAnchors[ConstraintWidget.ANCHOR_BASELINE].mTarget != null) {
443                 DependencyNode target =
444                         getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_BASELINE]);
445                 if (target != null) {
446                     addTarget(baseline, target, 0);
447                     addTarget(start, baseline, -1, mBaselineDimension);
448                     addTarget(end, start, 1, mDimension);
449                 }
450             } else {
451                 // no connections, nothing to do.
452                 if (!(mWidget instanceof Helper) && mWidget.getParent() != null) {
453                     DependencyNode top = mWidget.getParent().mVerticalRun.start;
454                     addTarget(start, top, mWidget.getY());
455                     addTarget(end, start, 1, mDimension);
456                     if (mWidget.hasBaseline()) {
457                         addTarget(baseline, start, 1, mBaselineDimension);
458                     }
459                     if (mDimensionBehavior == MATCH_CONSTRAINT) {
460                         if (mWidget.getDimensionRatio() > 0) {
461                             if (mWidget.mHorizontalRun.mDimensionBehavior == MATCH_CONSTRAINT) {
462                                 mWidget.mHorizontalRun.mDimension.mDependencies.add(mDimension);
463                                 mDimension.mTargets.add(mWidget.mHorizontalRun.mDimension);
464                                 mDimension.updateDelegate = this;
465                             }
466                         }
467                     }
468                 }
469             }
470 
471             // if dimension has no dependency, mark it as ready to solve
472             if (mDimension.mTargets.size() == 0) {
473                 mDimension.readyToSolve = true;
474             }
475         }
476     }
477 
478     // @TODO: add description
479     @Override
applyToWidget()480     public void applyToWidget() {
481         if (start.resolved) {
482             mWidget.setY(start.value);
483         }
484     }
485 }
486