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.state;
18 
19 import static androidx.constraintlayout.core.widgets.ConstraintWidget.CHAIN_PACKED;
20 import static androidx.constraintlayout.core.widgets.ConstraintWidget.CHAIN_SPREAD;
21 import static androidx.constraintlayout.core.widgets.ConstraintWidget.CHAIN_SPREAD_INSIDE;
22 
23 import androidx.constraintlayout.core.state.helpers.AlignHorizontallyReference;
24 import androidx.constraintlayout.core.state.helpers.AlignVerticallyReference;
25 import androidx.constraintlayout.core.state.helpers.BarrierReference;
26 import androidx.constraintlayout.core.state.helpers.FlowReference;
27 import androidx.constraintlayout.core.state.helpers.GridReference;
28 import androidx.constraintlayout.core.state.helpers.GuidelineReference;
29 import androidx.constraintlayout.core.state.helpers.HorizontalChainReference;
30 import androidx.constraintlayout.core.state.helpers.VerticalChainReference;
31 import androidx.constraintlayout.core.widgets.ConstraintWidget;
32 import androidx.constraintlayout.core.widgets.ConstraintWidgetContainer;
33 import androidx.constraintlayout.core.widgets.HelperWidget;
34 
35 import org.jspecify.annotations.NonNull;
36 
37 import java.util.ArrayList;
38 import java.util.HashMap;
39 import java.util.Map;
40 
41 /**
42  * Represents a full state of a ConstraintLayout
43  */
44 public class State {
45     private CorePixelDp mDpToPixel;
46     private boolean mIsLtr = true;
47     protected HashMap<Object, Reference> mReferences = new HashMap<>();
48     protected HashMap<Object, HelperReference> mHelperReferences = new HashMap<>();
49     HashMap<String, ArrayList<String>> mTags = new HashMap<>();
50 
51     static final int UNKNOWN = -1;
52     static final int CONSTRAINT_SPREAD = 0;
53     static final int CONSTRAINT_WRAP = 1;
54     static final int CONSTRAINT_RATIO = 2;
55 
56     public static final Integer PARENT = 0;
57 
58     public final ConstraintReference mParent = new ConstraintReference(this);
59 
60     public enum Constraint {
61         LEFT_TO_LEFT,
62         LEFT_TO_RIGHT,
63         RIGHT_TO_LEFT,
64         RIGHT_TO_RIGHT,
65         START_TO_START,
66         START_TO_END,
67         END_TO_START,
68         END_TO_END,
69         TOP_TO_TOP,
70         TOP_TO_BOTTOM,
71         TOP_TO_BASELINE,
72         BOTTOM_TO_TOP,
73         BOTTOM_TO_BOTTOM,
74         BOTTOM_TO_BASELINE,
75         BASELINE_TO_BASELINE,
76         BASELINE_TO_TOP,
77         BASELINE_TO_BOTTOM,
78         CENTER_HORIZONTALLY,
79         CENTER_VERTICALLY,
80         CIRCULAR_CONSTRAINT
81     }
82 
83     public enum Direction {
84         LEFT,
85         RIGHT,
86         START,
87         END,
88         TOP,
89         BOTTOM
90     }
91 
92     public enum Helper {
93         HORIZONTAL_CHAIN,
94         VERTICAL_CHAIN,
95         ALIGN_HORIZONTALLY,
96         ALIGN_VERTICALLY,
97         BARRIER,
98         LAYER,
99         HORIZONTAL_FLOW,
100         VERTICAL_FLOW,
101         GRID,
102         ROW,
103         COLUMN,
104         FLOW
105     }
106 
107     public enum Chain {
108         SPREAD,
109         SPREAD_INSIDE,
110         PACKED;
111 
112         public static Map<String, Chain> chainMap = new HashMap<>();
113         public static Map<String, Integer> valueMap = new HashMap<>();
114         static {
115             chainMap.put("packed", PACKED);
116             chainMap.put("spread_inside", SPREAD_INSIDE);
117             chainMap.put("spread", SPREAD);
118 
119             valueMap.put("packed", CHAIN_PACKED);
120             valueMap.put("spread_inside", CHAIN_SPREAD_INSIDE);
121             valueMap.put("spread", CHAIN_SPREAD);
122         }
123 
124         /**
125          * Get the Enum value with a String
126          * @param str a String representation of a Enum value
127          * @return a Enum value
128          */
getValueByString(String str)129         public static int getValueByString(String str) {
130             if (valueMap.containsKey(str)) {
131                 return valueMap.get(str);
132             }
133             return UNKNOWN;
134         }
135 
136         /**
137          * Get the actual int value with a String
138          * @param str a String representation of a Enum value
139          * @return an actual int value
140          */
getChainByString(String str)141         public static Chain getChainByString(String str) {
142             if (chainMap.containsKey(str)) {
143                 return chainMap.get(str);
144             }
145             return null;
146         }
147 
148     }
149 
150     public enum Wrap {
151         NONE,
152         CHAIN,
153         ALIGNED;
154         public static Map<String, Wrap> wrapMap = new HashMap<>();
155         public static Map<String, Integer> valueMap = new HashMap<>();
156         static {
157             wrapMap.put("none", NONE);
158             wrapMap.put("chain", CHAIN);
159             wrapMap.put("aligned", ALIGNED);
160 
161             valueMap.put("none", 0);
162             valueMap.put("chain", 3); // Corresponds to CHAIN_NEW
163             valueMap.put("aligned", 2);
164         }
165 
166         /**
167          * Get the actual int value with a String
168          * @param str a String representation of a Enum value
169          * @return a actual int value
170          */
getValueByString(String str)171         public static int getValueByString(String str) {
172             if (valueMap.containsKey(str)) {
173                 return valueMap.get(str);
174             }
175             return UNKNOWN;
176         }
177 
178         /**
179          * Get the Enum value with a String
180          * @param str a String representation of a Enum value
181          * @return a Enum value
182          */
getChainByString(String str)183         public static Wrap getChainByString(String str) {
184             if (wrapMap.containsKey(str)) {
185                 return wrapMap.get(str);
186             }
187             return null;
188         }
189     }
190 
State()191     public State() {
192         mParent.setKey(PARENT);
193         mReferences.put(PARENT, mParent);
194     }
195 
getDpToPixel()196     CorePixelDp getDpToPixel() {
197         return mDpToPixel;
198     }
199 
200     /**
201      * Set the function that converts dp to Pixels
202      */
setDpToPixel(CorePixelDp dpToPixel)203     public void setDpToPixel(CorePixelDp dpToPixel) {
204         this.mDpToPixel = dpToPixel;
205     }
206 
207     /**
208      * Set whether the layout direction is left to right (Ltr).
209      *
210      * @deprecated For consistency, use {@link #setRtl(boolean)} instead.
211      */
212     @Deprecated
setLtr(boolean isLtr)213     public void setLtr(boolean isLtr) {
214         mIsLtr = isLtr;
215     }
216 
217     /**
218      * Returns true if layout direction is left to right. False for right to left.
219      *
220      * @deprecated For consistency, use {@link #isRtl()} instead.
221      */
222     @Deprecated
isLtr()223     public boolean isLtr() {
224         return mIsLtr;
225     }
226 
227     /**
228      * Set whether the layout direction is right to left (Rtl).
229      */
setRtl(boolean isRtl)230     public void setRtl(boolean isRtl) {
231         mIsLtr = !isRtl;
232     }
233 
234     /**
235      * Returns true if layout direction is right to left. False for left to right.
236      */
isRtl()237     public boolean isRtl() {
238         return !mIsLtr;
239     }
240 
241     /**
242      * Clear the state
243      */
reset()244     public void reset() {
245         for (Object ref : mReferences.keySet()) {
246             mReferences.get(ref).getConstraintWidget().reset();
247         }
248         mReferences.clear();
249         mReferences.put(PARENT, mParent);
250         mHelperReferences.clear();
251         mTags.clear();
252         mBaselineNeeded.clear();
253         mDirtyBaselineNeededWidgets = true;
254     }
255 
256     /**
257      * Implements a conversion function for values, returning int.
258      * This can be used in case values (e.g. margins) are represented
259      * via an object, not directly an int.
260      *
261      * @param value the object to convert from
262      */
convertDimension(Object value)263     public int convertDimension(Object value) {
264         if (value instanceof Float) {
265             return Math.round((Float) value);
266         }
267         if (value instanceof Integer) {
268             return (Integer) value;
269         }
270         return 0;
271     }
272 
273     /**
274      * Create a new reference given a key.
275      */
createConstraintReference(Object key)276     public ConstraintReference createConstraintReference(Object key) {
277         return new ConstraintReference(this);
278     }
279 
280     // @TODO: add description
sameFixedWidth(int width)281     public boolean sameFixedWidth(int width) {
282         return mParent.getWidth().equalsFixedValue(width);
283     }
284 
285     // @TODO: add description
sameFixedHeight(int height)286     public boolean sameFixedHeight(int height) {
287         return mParent.getHeight().equalsFixedValue(height);
288     }
289 
290     // @TODO: add description
width(Dimension dimension)291     public State width(Dimension dimension) {
292         return setWidth(dimension);
293     }
294 
295     // @TODO: add description
height(Dimension dimension)296     public State height(Dimension dimension) {
297         return setHeight(dimension);
298     }
299 
300     // @TODO: add description
setWidth(Dimension dimension)301     public State setWidth(Dimension dimension) {
302         mParent.setWidth(dimension);
303         return this;
304     }
305 
306     // @TODO: add description
setHeight(Dimension dimension)307     public State setHeight(Dimension dimension) {
308         mParent.setHeight(dimension);
309         return this;
310     }
311 
reference(Object key)312     Reference reference(Object key) {
313         return mReferences.get(key);
314     }
315 
316     // @TODO: add description
constraints(Object key)317     public ConstraintReference constraints(Object key) {
318         Reference reference = mReferences.get(key);
319 
320         if (reference == null) {
321             reference = createConstraintReference(key);
322             mReferences.put(key, reference);
323             reference.setKey(key);
324         }
325         if (reference instanceof ConstraintReference) {
326             return (ConstraintReference) reference;
327         }
328         return null;
329     }
330 
331     private int mNumHelpers = 0;
332 
createHelperKey()333     private String createHelperKey() {
334         return "__HELPER_KEY_" + mNumHelpers++ + "__";
335     }
336 
337     // @TODO: add description
helper(Object key, State.Helper type)338     public HelperReference helper(Object key, State.Helper type) {
339         if (key == null) {
340             key = createHelperKey();
341         }
342         HelperReference reference = mHelperReferences.get(key);
343         if (reference == null) {
344             switch (type) {
345                 case HORIZONTAL_CHAIN: {
346                     reference = new HorizontalChainReference(this);
347                 }
348                 break;
349                 case VERTICAL_CHAIN: {
350                     reference = new VerticalChainReference(this);
351                 }
352                 break;
353                 case ALIGN_HORIZONTALLY: {
354                     reference = new AlignHorizontallyReference(this);
355                 }
356                 break;
357                 case ALIGN_VERTICALLY: {
358                     reference = new AlignVerticallyReference(this);
359                 }
360                 break;
361                 case BARRIER: {
362                     reference = new BarrierReference(this);
363                 }
364                 break;
365                 case VERTICAL_FLOW:
366                 case HORIZONTAL_FLOW: {
367                     reference = new FlowReference(this, type);
368                 }
369                 break;
370                 case GRID:
371                 case ROW:
372                 case COLUMN: {
373                     reference = new GridReference(this, type);
374                 }
375                     break;
376                 default: {
377                     reference = new HelperReference(this, type);
378                 }
379             }
380             reference.setKey(key);
381             mHelperReferences.put(key, reference);
382         }
383         return reference;
384     }
385 
386     // @TODO: add description
horizontalGuideline(Object key)387     public GuidelineReference horizontalGuideline(Object key) {
388         return guideline(key, ConstraintWidget.HORIZONTAL);
389     }
390 
391     // @TODO: add description
verticalGuideline(Object key)392     public GuidelineReference verticalGuideline(Object key) {
393         return guideline(key, ConstraintWidget.VERTICAL);
394     }
395 
396     // @TODO: add description
guideline(Object key, int orientation)397     public GuidelineReference guideline(Object key, int orientation) {
398         ConstraintReference reference = constraints(key);
399         if (reference.getFacade() == null
400                 || !(reference.getFacade() instanceof GuidelineReference)) {
401             GuidelineReference guidelineReference = new GuidelineReference(this);
402             guidelineReference.setOrientation(orientation);
403             guidelineReference.setKey(key);
404             reference.setFacade(guidelineReference);
405         }
406         return (GuidelineReference) reference.getFacade();
407     }
408 
409     // @TODO: add description
barrier(Object key, Direction direction)410     public BarrierReference barrier(Object key, Direction direction) {
411         ConstraintReference reference = constraints(key);
412         if (reference.getFacade() == null || !(reference.getFacade() instanceof BarrierReference)) {
413             BarrierReference barrierReference = new BarrierReference(this);
414             barrierReference.setBarrierDirection(direction);
415             reference.setFacade(barrierReference);
416         }
417         return (BarrierReference) reference.getFacade();
418     }
419 
420     /**
421      * Get a Grid reference
422      *
423      * @param key name of the reference object
424      * @param gridType type of Grid pattern - Grid, Row, or Column
425      * @return a GridReference object
426      */
getGrid(@onNull Object key, @NonNull String gridType)427     public @NonNull GridReference getGrid(@NonNull Object key, @NonNull String gridType) {
428         ConstraintReference reference = constraints(key);
429         if (reference.getFacade() == null || !(reference.getFacade() instanceof GridReference)) {
430             State.Helper Type = Helper.GRID;
431             if (gridType.charAt(0) == 'r') {
432                 Type = Helper.ROW;
433             } else if (gridType.charAt(0) == 'c') {
434                 Type = Helper.COLUMN;
435             }
436             GridReference gridReference = new GridReference(this, Type);
437             reference.setFacade(gridReference);
438         }
439         return (GridReference) reference.getFacade();
440     }
441 
442     /**
443      * Gets a reference to a Flow object. Creating it if needed.
444      * @param key id of the reference
445      * @param vertical is it a vertical or horizontal flow
446      * @return a FlowReference
447      */
getFlow(Object key, boolean vertical)448     public FlowReference getFlow(Object key, boolean vertical) {
449         ConstraintReference reference = constraints(key);
450         if (reference.getFacade() == null || !(reference.getFacade() instanceof FlowReference)) {
451             FlowReference flowReference =
452                     vertical ? new FlowReference(this, Helper.VERTICAL_FLOW)
453                             : new FlowReference(this, Helper.HORIZONTAL_FLOW);
454 
455             reference.setFacade(flowReference);
456         }
457         return (FlowReference) reference.getFacade();
458     }
459 
460     // @TODO: add description
verticalChain()461     public VerticalChainReference verticalChain() {
462         return (VerticalChainReference) helper(null, Helper.VERTICAL_CHAIN);
463     }
464 
465     // @TODO: add description
verticalChain(Object... references)466     public VerticalChainReference verticalChain(Object... references) {
467         VerticalChainReference reference =
468                 (VerticalChainReference) helper(null, State.Helper.VERTICAL_CHAIN);
469         reference.add(references);
470         return reference;
471     }
472 
473     // @TODO: add description
horizontalChain()474     public HorizontalChainReference horizontalChain() {
475         return (HorizontalChainReference) helper(null, Helper.HORIZONTAL_CHAIN);
476     }
477 
478     // @TODO: add description
horizontalChain(Object... references)479     public HorizontalChainReference horizontalChain(Object... references) {
480         HorizontalChainReference reference =
481                 (HorizontalChainReference) helper(null, Helper.HORIZONTAL_CHAIN);
482         reference.add(references);
483         return reference;
484     }
485 
486     /**
487      * Get a VerticalFlowReference
488      *
489      * @return a VerticalFlowReference
490      */
getVerticalFlow()491     public FlowReference getVerticalFlow() {
492         return (FlowReference) helper(null, Helper.VERTICAL_FLOW);
493     }
494 
495     /**
496      * Get a VerticalFlowReference and add it to references
497      *
498      * @param references where we add the VerticalFlowReference
499      * @return a VerticalFlowReference
500      */
getVerticalFlow(Object... references)501     public FlowReference getVerticalFlow(Object... references) {
502         FlowReference reference =
503                 (FlowReference) helper(null, State.Helper.VERTICAL_FLOW);
504         reference.add(references);
505         return reference;
506     }
507 
508     /**
509      * Get a HorizontalFlowReference
510      *
511      * @return a HorizontalFlowReference
512      */
getHorizontalFlow()513     public FlowReference getHorizontalFlow() {
514         return (FlowReference) helper(null, Helper.HORIZONTAL_FLOW);
515     }
516 
517     /**
518      * Get a HorizontalFlowReference and add it to references
519      *
520      * @param references references where we the HorizontalFlowReference
521      * @return a HorizontalFlowReference
522      */
getHorizontalFlow(Object... references)523     public FlowReference getHorizontalFlow(Object... references) {
524         FlowReference reference =
525                 (FlowReference) helper(null, Helper.HORIZONTAL_FLOW);
526         reference.add(references);
527         return reference;
528     }
529 
530     // @TODO: add description
centerHorizontally(Object... references)531     public AlignHorizontallyReference centerHorizontally(Object... references) {
532         AlignHorizontallyReference reference =
533                 (AlignHorizontallyReference) helper(null, Helper.ALIGN_HORIZONTALLY);
534         reference.add(references);
535         return reference;
536     }
537 
538     // @TODO: add description
centerVertically(Object... references)539     public AlignVerticallyReference centerVertically(Object... references) {
540         AlignVerticallyReference reference =
541                 (AlignVerticallyReference) helper(null, Helper.ALIGN_VERTICALLY);
542         reference.add(references);
543         return reference;
544     }
545 
546     // @TODO: add description
directMapping()547     public void directMapping() {
548         for (Object key : mReferences.keySet()) {
549             Reference ref = constraints(key);
550             if (!(ref instanceof ConstraintReference)) {
551                 continue;
552             }
553             ConstraintReference reference = (ConstraintReference) ref;
554             reference.setView(key);
555         }
556     }
557 
558     // @TODO: add description
map(Object key, Object view)559     public void map(Object key, Object view) {
560         ConstraintReference ref = constraints(key);
561         if (ref != null) {
562             ref.setView(view);
563         }
564     }
565 
566     // @TODO: add description
setTag(String key, String tag)567     public void setTag(String key, String tag) {
568         Reference ref = constraints(key);
569         if (ref instanceof ConstraintReference) {
570             ConstraintReference reference = (ConstraintReference) ref;
571             reference.setTag(tag);
572             ArrayList<String> list = null;
573             if (!mTags.containsKey(tag)) {
574                 list = new ArrayList<>();
575                 mTags.put(tag, list);
576             } else {
577                 list = mTags.get(tag);
578             }
579             list.add(key);
580         }
581     }
582 
583     // @TODO: add description
getIdsForTag(String tag)584     public ArrayList<String> getIdsForTag(String tag) {
585         if (mTags.containsKey(tag)) {
586             return mTags.get(tag);
587         }
588         return null;
589     }
590 
591     // @TODO: add description
apply(ConstraintWidgetContainer container)592     public void apply(ConstraintWidgetContainer container) {
593         container.removeAllChildren();
594         mParent.getWidth().apply(this, container, ConstraintWidget.HORIZONTAL);
595         mParent.getHeight().apply(this, container, ConstraintWidget.VERTICAL);
596         // add helper references
597         for (Object key : mHelperReferences.keySet()) {
598             HelperReference reference = mHelperReferences.get(key);
599             HelperWidget helperWidget = reference.getHelperWidget();
600             if (helperWidget != null) {
601                 Reference constraintReference = mReferences.get(key);
602                 if (constraintReference == null) {
603                     constraintReference = constraints(key);
604                 }
605                 constraintReference.setConstraintWidget(helperWidget);
606             }
607         }
608         for (Object key : mReferences.keySet()) {
609             Reference reference = mReferences.get(key);
610             if (reference != mParent && reference.getFacade() instanceof HelperReference) {
611                 HelperWidget helperWidget =
612                         ((HelperReference) reference.getFacade()).getHelperWidget();
613                 if (helperWidget != null) {
614                     Reference constraintReference = mReferences.get(key);
615                     if (constraintReference == null) {
616                         constraintReference = constraints(key);
617                     }
618                     constraintReference.setConstraintWidget(helperWidget);
619                 }
620             }
621         }
622         for (Object key : mReferences.keySet()) {
623             Reference reference = mReferences.get(key);
624             if (reference != mParent) {
625                 ConstraintWidget widget = reference.getConstraintWidget();
626                 widget.setDebugName(reference.getKey().toString());
627                 widget.setParent(null);
628                 if (reference.getFacade() instanceof GuidelineReference) {
629                     // we apply Guidelines first to correctly setup their ConstraintWidget.
630                     reference.apply();
631                 }
632                 container.add(widget);
633             } else {
634                 reference.setConstraintWidget(container);
635             }
636         }
637         for (Object key : mHelperReferences.keySet()) {
638             // We need this pass to apply chains properly
639             HelperReference reference = mHelperReferences.get(key);
640             HelperWidget helperWidget = reference.getHelperWidget();
641             if (helperWidget != null) {
642                 for (Object keyRef : reference.mReferences) {
643                     Reference constraintReference = mReferences.get(keyRef);
644                     reference.getHelperWidget().add(constraintReference.getConstraintWidget());
645                 }
646                 reference.apply();
647             } else {
648                 reference.apply();
649             }
650         }
651         for (Object key : mReferences.keySet()) {
652             Reference reference = mReferences.get(key);
653             if (reference != mParent && reference.getFacade() instanceof HelperReference) {
654                 HelperReference helperReference = (HelperReference) reference.getFacade();
655                 HelperWidget helperWidget = helperReference.getHelperWidget();
656                 if (helperWidget != null) {
657                     for (Object keyRef : helperReference.mReferences) {
658                         Reference constraintReference = mReferences.get(keyRef);
659                         if (constraintReference != null) {
660                             helperWidget.add(constraintReference.getConstraintWidget());
661                         } else if (keyRef instanceof Reference) {
662                             helperWidget.add(((Reference) keyRef).getConstraintWidget());
663                         } else {
664                             System.out.println("couldn't find reference for " + keyRef);
665                         }
666                     }
667                     reference.apply();
668                 }
669             }
670         }
671         for (Object key : mReferences.keySet()) {
672             Reference reference = mReferences.get(key);
673             reference.apply();
674             ConstraintWidget widget = reference.getConstraintWidget();
675             if (widget != null && key != null) {
676                 widget.stringId = key.toString();
677             }
678         }
679     }
680 
681     // ================= add baseline code================================
682     ArrayList<Object> mBaselineNeeded = new ArrayList<>();
683     ArrayList<ConstraintWidget> mBaselineNeededWidgets = new ArrayList<>();
684     boolean mDirtyBaselineNeededWidgets = true;
685 
686     /**
687      * Baseline is needed for this object
688      */
baselineNeededFor(Object id)689     public void baselineNeededFor(Object id) {
690         mBaselineNeeded.add(id);
691         mDirtyBaselineNeededWidgets = true;
692     }
693 
694     /**
695      * Does this constraintWidget need a baseline
696      *
697      * @return true if the constraintWidget needs a baseline
698      */
isBaselineNeeded(ConstraintWidget constraintWidget)699     public boolean isBaselineNeeded(ConstraintWidget constraintWidget) {
700         if (mDirtyBaselineNeededWidgets) {
701             mBaselineNeededWidgets.clear();
702             for (Object id : mBaselineNeeded) {
703                 ConstraintWidget widget = mReferences.get(id).getConstraintWidget();
704                 if (widget != null) mBaselineNeededWidgets.add(widget);
705             }
706 
707             mDirtyBaselineNeededWidgets = false;
708         }
709         return mBaselineNeededWidgets.contains(constraintWidget);
710     }
711 }
712