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.MATCH_CONSTRAINT;
20 import static androidx.constraintlayout.core.widgets.ConstraintWidget.GONE;
21 import static androidx.constraintlayout.core.widgets.ConstraintWidget.HORIZONTAL;
22 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_WRAP;
23 import static androidx.constraintlayout.core.widgets.ConstraintWidget.VERTICAL;
24 
25 import androidx.constraintlayout.core.widgets.ConstraintAnchor;
26 import androidx.constraintlayout.core.widgets.ConstraintWidget;
27 import androidx.constraintlayout.core.widgets.ConstraintWidgetContainer;
28 
29 import java.util.ArrayList;
30 
31 public class ChainRun extends WidgetRun {
32     ArrayList<WidgetRun> mWidgets = new ArrayList<>();
33     private int mChainStyle;
34 
ChainRun(ConstraintWidget widget, int orientation)35     public ChainRun(ConstraintWidget widget, int orientation) {
36         super(widget);
37         this.orientation = orientation;
38         build();
39     }
40 
41     @Override
toString()42     public String toString() {
43         StringBuilder log = new StringBuilder("ChainRun ");
44         log.append((orientation == HORIZONTAL ? "horizontal : " : "vertical : "));
45         for (WidgetRun run : mWidgets) {
46             log.append("<");
47             log.append(run);
48             log.append("> ");
49         }
50         return log.toString();
51     }
52 
53     @Override
supportsWrapComputation()54     boolean supportsWrapComputation() {
55         final int count = mWidgets.size();
56         for (int i = 0; i < count; i++) {
57             WidgetRun run = mWidgets.get(i);
58             if (!run.supportsWrapComputation()) {
59                 return false;
60             }
61         }
62         return true;
63     }
64 
65     // @TODO: add description
66     @Override
getWrapDimension()67     public long getWrapDimension() {
68         final int count = mWidgets.size();
69         long wrapDimension = 0;
70         for (int i = 0; i < count; i++) {
71             WidgetRun run = mWidgets.get(i);
72             wrapDimension += run.start.mMargin;
73             wrapDimension += run.getWrapDimension();
74             wrapDimension += run.end.mMargin;
75         }
76         return wrapDimension;
77     }
78 
build()79     private void build() {
80         ConstraintWidget current = mWidget;
81         ConstraintWidget previous = current.getPreviousChainMember(orientation);
82         while (previous != null) {
83             current = previous;
84             previous = current.getPreviousChainMember(orientation);
85         }
86         mWidget = current; // first element of the chain
87         mWidgets.add(current.getRun(orientation));
88         ConstraintWidget next = current.getNextChainMember(orientation);
89         while (next != null) {
90             current = next;
91             mWidgets.add(current.getRun(orientation));
92             next = current.getNextChainMember(orientation);
93         }
94         for (WidgetRun run : mWidgets) {
95             if (orientation == HORIZONTAL) {
96                 run.mWidget.horizontalChainRun = this;
97             } else if (orientation == ConstraintWidget.VERTICAL) {
98                 run.mWidget.verticalChainRun = this;
99             }
100         }
101         boolean isInRtl = (orientation == HORIZONTAL)
102                 && ((ConstraintWidgetContainer) mWidget.getParent()).isRtl();
103         if (isInRtl && mWidgets.size() > 1) {
104             mWidget = mWidgets.get(mWidgets.size() - 1).mWidget;
105         }
106         mChainStyle = orientation == HORIZONTAL
107                 ? mWidget.getHorizontalChainStyle() : mWidget.getVerticalChainStyle();
108     }
109 
110 
111     @Override
clear()112     void clear() {
113         mRunGroup = null;
114         for (WidgetRun run : mWidgets) {
115             run.clear();
116         }
117     }
118 
119     @Override
reset()120     void reset() {
121         start.resolved = false;
122         end.resolved = false;
123     }
124 
125     @Override
update(Dependency dependency)126     public void update(Dependency dependency) {
127         if (!(start.resolved && end.resolved)) {
128             return;
129         }
130 
131         ConstraintWidget parent = mWidget.getParent();
132         boolean isInRtl = false;
133         if (parent instanceof ConstraintWidgetContainer) {
134             isInRtl = ((ConstraintWidgetContainer) parent).isRtl();
135         }
136         int distance = end.value - start.value;
137         int size = 0;
138         int numMatchConstraints = 0;
139         float weights = 0;
140         int numVisibleWidgets = 0;
141         final int count = mWidgets.size();
142         // let's find the first visible widget...
143         int firstVisibleWidget = -1;
144         for (int i = 0; i < count; i++) {
145             WidgetRun run = mWidgets.get(i);
146             if (run.mWidget.getVisibility() == GONE) {
147                 continue;
148             }
149             firstVisibleWidget = i;
150             break;
151         }
152         // now the last visible widget...
153         int lastVisibleWidget = -1;
154         for (int i = count - 1; i >= 0; i--) {
155             WidgetRun run = mWidgets.get(i);
156             if (run.mWidget.getVisibility() == GONE) {
157                 continue;
158             }
159             lastVisibleWidget = i;
160             break;
161         }
162         for (int j = 0; j < 2; j++) {
163             for (int i = 0; i < count; i++) {
164                 WidgetRun run = mWidgets.get(i);
165                 if (run.mWidget.getVisibility() == GONE) {
166                     continue;
167                 }
168                 numVisibleWidgets++;
169                 if (i > 0 && i >= firstVisibleWidget) {
170                     size += run.start.mMargin;
171                 }
172                 int dimension = run.mDimension.value;
173                 boolean treatAsFixed = run.mDimensionBehavior != MATCH_CONSTRAINT;
174                 if (treatAsFixed) {
175                     if (orientation == HORIZONTAL
176                             && !run.mWidget.mHorizontalRun.mDimension.resolved) {
177                         return;
178                     }
179                     if (orientation == VERTICAL && !run.mWidget.mVerticalRun.mDimension.resolved) {
180                         return;
181                     }
182                 } else if (run.matchConstraintsType == MATCH_CONSTRAINT_WRAP && j == 0) {
183                     treatAsFixed = true;
184                     dimension = run.mDimension.wrapValue;
185                     numMatchConstraints++;
186                 } else if (run.mDimension.resolved) {
187                     treatAsFixed = true;
188                 }
189                 if (!treatAsFixed) { // only for the first pass
190                     numMatchConstraints++;
191                     float weight = run.mWidget.mWeight[orientation];
192                     if (weight >= 0) {
193                         weights += weight;
194                     }
195                 } else {
196                     size += dimension;
197                 }
198                 if (i < count - 1 && i < lastVisibleWidget) {
199                     size += -run.end.mMargin;
200                 }
201             }
202             if (size < distance || numMatchConstraints == 0) {
203                 break; // we are good to go!
204             }
205             // otherwise, let's do another pass with using match_constraints
206             numVisibleWidgets = 0;
207             numMatchConstraints = 0;
208             size = 0;
209             weights = 0;
210         }
211 
212         int position = start.value;
213         if (isInRtl) {
214             position = end.value;
215         }
216         if (size > distance) {
217             if (isInRtl) {
218                 position += (int) (0.5f + (size - distance) / 2f);
219             } else {
220                 position -= (int) (0.5f + (size - distance) / 2f);
221             }
222         }
223         int matchConstraintsDimension = 0;
224         if (numMatchConstraints > 0) {
225             matchConstraintsDimension =
226                     (int) (0.5f + (distance - size) / (float) numMatchConstraints);
227 
228             int appliedLimits = 0;
229             for (int i = 0; i < count; i++) {
230                 WidgetRun run = mWidgets.get(i);
231                 if (run.mWidget.getVisibility() == GONE) {
232                     continue;
233                 }
234                 if (run.mDimensionBehavior == MATCH_CONSTRAINT && !run.mDimension.resolved) {
235                     int dimension = matchConstraintsDimension;
236                     if (weights > 0) {
237                         float weight = run.mWidget.mWeight[orientation];
238                         dimension = (int) (0.5f + weight * (distance - size) / weights);
239                     }
240                     int max;
241                     int min;
242                     int value = dimension;
243                     if (orientation == HORIZONTAL) {
244                         max = run.mWidget.mMatchConstraintMaxWidth;
245                         min = run.mWidget.mMatchConstraintMinWidth;
246                     } else {
247                         max = run.mWidget.mMatchConstraintMaxHeight;
248                         min = run.mWidget.mMatchConstraintMinHeight;
249                     }
250                     if (run.matchConstraintsType == MATCH_CONSTRAINT_WRAP) {
251                         value = Math.min(value, run.mDimension.wrapValue);
252                     }
253                     value = Math.max(min, value);
254                     if (max > 0) {
255                         value = Math.min(max, value);
256                     }
257                     if (value != dimension) {
258                         appliedLimits++;
259                         dimension = value;
260                     }
261                     run.mDimension.resolve(dimension);
262                 }
263             }
264             if (appliedLimits > 0) {
265                 numMatchConstraints -= appliedLimits;
266                 // we have to recompute the sizes
267                 size = 0;
268                 for (int i = 0; i < count; i++) {
269                     WidgetRun run = mWidgets.get(i);
270                     if (run.mWidget.getVisibility() == GONE) {
271                         continue;
272                     }
273                     if (i > 0 && i >= firstVisibleWidget) {
274                         size += run.start.mMargin;
275                     }
276                     size += run.mDimension.value;
277                     if (i < count - 1 && i < lastVisibleWidget) {
278                         size += -run.end.mMargin;
279                     }
280                 }
281             }
282             if (mChainStyle == ConstraintWidget.CHAIN_PACKED && appliedLimits == 0) {
283                 mChainStyle = ConstraintWidget.CHAIN_SPREAD;
284             }
285         }
286 
287         if (size > distance) {
288             mChainStyle = ConstraintWidget.CHAIN_PACKED;
289         }
290 
291         if (numVisibleWidgets > 0 && numMatchConstraints == 0
292                 && firstVisibleWidget == lastVisibleWidget) {
293             // only one widget of fixed size to display...
294             mChainStyle = ConstraintWidget.CHAIN_PACKED;
295         }
296 
297         if (mChainStyle == ConstraintWidget.CHAIN_SPREAD_INSIDE) {
298             int gap = 0;
299             if (numVisibleWidgets > 1) {
300                 gap = (distance - size) / (numVisibleWidgets - 1);
301             } else if (numVisibleWidgets == 1) {
302                 gap = (distance - size) / 2;
303             }
304             if (numMatchConstraints > 0) {
305                 gap = 0;
306             }
307             for (int i = 0; i < count; i++) {
308                 int index = i;
309                 if (isInRtl) {
310                     index = count - (i + 1);
311                 }
312                 WidgetRun run = mWidgets.get(index);
313                 if (run.mWidget.getVisibility() == GONE) {
314                     run.start.resolve(position);
315                     run.end.resolve(position);
316                     continue;
317                 }
318                 if (i > 0) {
319                     if (isInRtl) {
320                         position -= gap;
321                     } else {
322                         position += gap;
323                     }
324                 }
325                 if (i > 0 && i >= firstVisibleWidget) {
326                     if (isInRtl) {
327                         position -= run.start.mMargin;
328                     } else {
329                         position += run.start.mMargin;
330                     }
331                 }
332 
333                 if (isInRtl) {
334                     run.end.resolve(position);
335                 } else {
336                     run.start.resolve(position);
337                 }
338 
339                 int dimension = run.mDimension.value;
340                 if (run.mDimensionBehavior == MATCH_CONSTRAINT
341                         && run.matchConstraintsType == MATCH_CONSTRAINT_WRAP) {
342                     dimension = run.mDimension.wrapValue;
343                 }
344                 if (isInRtl) {
345                     position -= dimension;
346                 } else {
347                     position += dimension;
348                 }
349 
350                 if (isInRtl) {
351                     run.start.resolve(position);
352                 } else {
353                     run.end.resolve(position);
354                 }
355                 run.mResolved = true;
356                 if (i < count - 1 && i < lastVisibleWidget) {
357                     if (isInRtl) {
358                         position -= -run.end.mMargin;
359                     } else {
360                         position += -run.end.mMargin;
361                     }
362                 }
363             }
364         } else if (mChainStyle == ConstraintWidget.CHAIN_SPREAD) {
365             int gap = (distance - size) / (numVisibleWidgets + 1);
366             if (numMatchConstraints > 0) {
367                 gap = 0;
368             }
369             for (int i = 0; i < count; i++) {
370                 int index = i;
371                 if (isInRtl) {
372                     index = count - (i + 1);
373                 }
374                 WidgetRun run = mWidgets.get(index);
375                 if (run.mWidget.getVisibility() == GONE) {
376                     run.start.resolve(position);
377                     run.end.resolve(position);
378                     continue;
379                 }
380                 if (isInRtl) {
381                     position -= gap;
382                 } else {
383                     position += gap;
384                 }
385                 if (i > 0 && i >= firstVisibleWidget) {
386                     if (isInRtl) {
387                         position -= run.start.mMargin;
388                     } else {
389                         position += run.start.mMargin;
390                     }
391                 }
392 
393                 if (isInRtl) {
394                     run.end.resolve(position);
395                 } else {
396                     run.start.resolve(position);
397                 }
398 
399                 int dimension = run.mDimension.value;
400                 if (run.mDimensionBehavior == MATCH_CONSTRAINT
401                         && run.matchConstraintsType == MATCH_CONSTRAINT_WRAP) {
402                     dimension = Math.min(dimension, run.mDimension.wrapValue);
403                 }
404 
405                 if (isInRtl) {
406                     position -= dimension;
407                 } else {
408                     position += dimension;
409                 }
410 
411                 if (isInRtl) {
412                     run.start.resolve(position);
413                 } else {
414                     run.end.resolve(position);
415                 }
416                 if (i < count - 1 && i < lastVisibleWidget) {
417                     if (isInRtl) {
418                         position -= -run.end.mMargin;
419                     } else {
420                         position += -run.end.mMargin;
421                     }
422                 }
423             }
424         } else if (mChainStyle == ConstraintWidget.CHAIN_PACKED) {
425             float bias = (orientation == HORIZONTAL) ? mWidget.getHorizontalBiasPercent()
426                     : mWidget.getVerticalBiasPercent();
427             if (isInRtl) {
428                 bias = 1 - bias;
429             }
430             int gap = (int) (0.5f + (distance - size) * bias);
431             if (gap < 0 || numMatchConstraints > 0) {
432                 gap = 0;
433             }
434             if (isInRtl) {
435                 position -= gap;
436             } else {
437                 position += gap;
438             }
439             for (int i = 0; i < count; i++) {
440                 int index = i;
441                 if (isInRtl) {
442                     index = count - (i + 1);
443                 }
444                 WidgetRun run = mWidgets.get(index);
445                 if (run.mWidget.getVisibility() == GONE) {
446                     run.start.resolve(position);
447                     run.end.resolve(position);
448                     continue;
449                 }
450                 if (i > 0 && i >= firstVisibleWidget) {
451                     if (isInRtl) {
452                         position -= run.start.mMargin;
453                     } else {
454                         position += run.start.mMargin;
455                     }
456                 }
457                 if (isInRtl) {
458                     run.end.resolve(position);
459                 } else {
460                     run.start.resolve(position);
461                 }
462 
463                 int dimension = run.mDimension.value;
464                 if (run.mDimensionBehavior == MATCH_CONSTRAINT
465                         && run.matchConstraintsType == MATCH_CONSTRAINT_WRAP) {
466                     dimension = run.mDimension.wrapValue;
467                 }
468                 if (isInRtl) {
469                     position -= dimension;
470                 } else {
471                     position += dimension;
472                 }
473 
474                 if (isInRtl) {
475                     run.start.resolve(position);
476                 } else {
477                     run.end.resolve(position);
478                 }
479                 if (i < count - 1 && i < lastVisibleWidget) {
480                     if (isInRtl) {
481                         position -= -run.end.mMargin;
482                     } else {
483                         position += -run.end.mMargin;
484                     }
485                 }
486             }
487         }
488     }
489 
490     // @TODO: add description
491     @Override
applyToWidget()492     public void applyToWidget() {
493         for (int i = 0; i < mWidgets.size(); i++) {
494             WidgetRun run = mWidgets.get(i);
495             run.applyToWidget();
496         }
497     }
498 
getFirstVisibleWidget()499     private ConstraintWidget getFirstVisibleWidget() {
500         for (int i = 0; i < mWidgets.size(); i++) {
501             WidgetRun run = mWidgets.get(i);
502             if (run.mWidget.getVisibility() != GONE) {
503                 return run.mWidget;
504             }
505         }
506         return null;
507     }
508 
getLastVisibleWidget()509     private ConstraintWidget getLastVisibleWidget() {
510         for (int i = mWidgets.size() - 1; i >= 0; i--) {
511             WidgetRun run = mWidgets.get(i);
512             if (run.mWidget.getVisibility() != GONE) {
513                 return run.mWidget;
514             }
515         }
516         return null;
517     }
518 
519 
520     @Override
apply()521     void apply() {
522         for (WidgetRun run : mWidgets) {
523             run.apply();
524         }
525         int count = mWidgets.size();
526         if (count < 1) {
527             return;
528         }
529 
530         // get the first and last element of the chain
531         ConstraintWidget firstWidget = mWidgets.get(0).mWidget;
532         ConstraintWidget lastWidget = mWidgets.get(count - 1).mWidget;
533 
534         if (orientation == HORIZONTAL) {
535             ConstraintAnchor startAnchor = firstWidget.mLeft;
536             ConstraintAnchor endAnchor = lastWidget.mRight;
537             DependencyNode startTarget = getTarget(startAnchor, HORIZONTAL);
538             int startMargin = startAnchor.getMargin();
539             ConstraintWidget firstVisibleWidget = getFirstVisibleWidget();
540             if (firstVisibleWidget != null) {
541                 startMargin = firstVisibleWidget.mLeft.getMargin();
542             }
543             if (startTarget != null) {
544                 addTarget(start, startTarget, startMargin);
545             }
546             DependencyNode endTarget = getTarget(endAnchor, HORIZONTAL);
547             int endMargin = endAnchor.getMargin();
548             ConstraintWidget lastVisibleWidget = getLastVisibleWidget();
549             if (lastVisibleWidget != null) {
550                 endMargin = lastVisibleWidget.mRight.getMargin();
551             }
552             if (endTarget != null) {
553                 addTarget(end, endTarget, -endMargin);
554             }
555         } else {
556             ConstraintAnchor startAnchor = firstWidget.mTop;
557             ConstraintAnchor endAnchor = lastWidget.mBottom;
558             DependencyNode startTarget = getTarget(startAnchor, VERTICAL);
559             int startMargin = startAnchor.getMargin();
560             ConstraintWidget firstVisibleWidget = getFirstVisibleWidget();
561             if (firstVisibleWidget != null) {
562                 startMargin = firstVisibleWidget.mTop.getMargin();
563             }
564             if (startTarget != null) {
565                 addTarget(start, startTarget, startMargin);
566             }
567             DependencyNode endTarget = getTarget(endAnchor, VERTICAL);
568             int endMargin = endAnchor.getMargin();
569             ConstraintWidget lastVisibleWidget = getLastVisibleWidget();
570             if (lastVisibleWidget != null) {
571                 endMargin = lastVisibleWidget.mBottom.getMargin();
572             }
573             if (endTarget != null) {
574                 addTarget(end, endTarget, -endMargin);
575             }
576         }
577         start.updateDelegate = this;
578         end.updateDelegate = this;
579     }
580 
581 }
582